Developer Integration Guide

Wire Axiom into your MCP agent stack

Three integration scenarios, complete API reference, receipt schema, and security model. Written for engineers at regulated enterprises.

1
Architecture
2
Scenarios
3
API reference
4
Receipt schema
5
Security model
6
Production checklist

Architecture Overview

Axiom sits in the invocation path between your agent and its skill backend. Every skill call passes through the Axiom receipt middleware, which captures a cryptographic snapshot before and after execution.

┌─────────────┐ ┌──────────────────────┐ ┌──────────────┐ ┌────────────────┐ │ Agent │ │ Axiom Receipt Layer │ │ MCP Server │ │ Skill Backend │ │ (your code) │──────▶│ hash inputs │──────▶│ (stdin/stdout)│──────▶│ (tool handler) │ │ │◀──────│ sign receipt │◀──────│ │◀──────│ │ └─────────────┘ └──────────────────────┘ └──────────────┘ └────────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ PostgreSQL │ │ Receipt │ │ receipts tbl │ │ stored │ └──────────────┘ └──────────────┘

The receipt captures four elements in a tamper-evident chain:

  1. inputs_hash — SHA-256 of skill inputs, computed before execution.
  2. output_hash — SHA-256 of skill output, computed after execution.
  3. signature — HMAC-SHA256 over the full receipt payload.
  4. timestamp — UTC time of invocation initiation.

The signing secret never leaves your infrastructure. Axiom operates in two modes: hosted (signing handled by Axiom's service) and self-hosted (signing secret deployed alongside your agent). The receipt ID is the only lookup key needed for independent verification.

You don't need to trust Axiom to verify a receipt. The signature is a standard HMAC-SHA256 that you can recompute independently using the receipt payload and your copy of the signing secret.

Three Integration Scenarios

Scenario A — Standalone MCP Server

Run Axiom as a sidecar process alongside your MCP server. Axiom's MCP client proxies tool calls: invoke skill → capture receipt → return result + receipt. Minimal code change: point your agent at Axiom's MCP endpoint.

Your Agent → Axiom Sidecar → Your MCP Server (receipts) (signs)

Setup

bash
# Run the Axiom MCP test server alongside your own server
node mcp-servers/axiom-test-server.js &
node your-mcp-server.js

Wrapping an existing MCP server

javascript
// wrap-mcp-server.js
const { MCPClient } = require('./services/mcp');

// Point Axiom at your existing MCP server
const client = new MCPClient('node', ['./my-mcp-server.js']);

async function init() {
  await client.connect();

  // Every call through this client is now receipt-stamped
  const { output, receipt } = await client.callTool(
    'my-tool',
    { param: 'value' },
    { source: 'live', caller: 'my-agent-v1' }
  );

  console.log('Receipt ID:', receipt.id);
  console.log('Verify:', `https://axiom-ai-19.polsia.app/verify?id=${receipt.id}`);
}

init();
The callTool method handles input hashing, MCP communication, output hashing, signing, and DB persistence automatically. You get the receipt back alongside the tool result.

Scenario B — Claude Agent Harness (Claude Code, Claude CLI)

Wrap skill invocations in the Claude agent's tool-use layer. Before calling the tool: compute input hash, store pending receipt. After receiving response: compute output hash, sign receipt, persist.

Implementation pattern (Node.js)

javascript
// axiom-tool-wrapper.js
const { hashInputs, hashOutput, signReceipt } = require('./services/crypto');
const { createReceipt } = require('./db/receipts');

// Call this BEFORE every tool invocation
async function beforeSkillCall(skillName, inputs, caller) {
  const timestamp = new Date().toISOString();
  const inputsHash = hashInputs({ skill: skillName, args: inputs });
  return { timestamp, inputsHash }; // store pending state
}

// Call this AFTER every tool invocation
async function afterSkillCall(beforeState, skillName, output, caller) {
  const { timestamp, inputsHash } = beforeState;
  const outputHash = hashOutput(output);
  const signature = signReceipt(skillName, timestamp, inputsHash, outputHash);

  const receipt = await createReceipt({
    skill_name: skillName,
    timestamp,
    inputs_hash: inputsHash,
    output_hash: outputHash,
    signature,
    verification_status: 'verified',
    source: 'live',
    caller: caller || null,
  });

  return receipt;
}

// ── Example harness integration ──────────────────────────────
// In your Claude Code tool handler:
async function callToolWithReceipt(toolName, toolArgs, caller) {
  const before = await beforeSkillCall(toolName, toolArgs, caller);
  const rawResult = await yourMCPClient.callTool(toolName, toolArgs);
  const receipt = await afterSkillCall(before, toolName, rawResult, caller);
  return { result: rawResult, receipt };
}

In the Claude Code config (claude_desktop_config.json)

json
{
  "mcpServers": {
    "axiom": {
      "command": "node",
      "args": ["/path/to/mcp-servers/axiom-test-server.js"]
    }
  }
}

Axiom's MCP server returns receipt metadata alongside tool results. Configure your harness to extract and store receipts on every tool call.

Scenario C — Custom Agent Framework

Integrate as middleware in your existing agent execution loop. Two functions to add to your agent's skill invocation path:

beforeSkillCall
javascript
/**
 * Called before every skill invocation.
 * @param {string} skill - skill name
 * @param {object} inputs - skill inputs
 * @returns {{timestamp, inputsHash, skill, inputs}}
 */
function beforeSkillCall(skill, inputs) {
  const timestamp = new Date().toISOString();
  const inputsHash = hashInputs({ skill, args: inputs });
  return { timestamp, inputsHash, skill, inputs };
}
afterSkillCall
javascript
/**
 * Called after every skill invocation.
 * @param {object} beforeState - result of beforeSkillCall
 * @param {object} result - skill execution result
 * @param {string} [caller] - optional caller ID
 * @returns {Promise<object>} - stored receipt
 */
async function afterSkillCall(beforeState, result, caller) {
  const outputHash = hashOutput(result);
  const signature = signReceipt(
    beforeState.skill,
    beforeState.timestamp,
    beforeState.inputsHash,
    outputHash
  );
  return createReceipt({
    skill_name: beforeState.skill,
    timestamp: beforeState.timestamp,
    inputs_hash: beforeState.inputsHash,
    output_hash: outputHash,
    signature,
    verification_status: 'verified',
    source: 'live',
    caller: caller || null,
  });
}

Wiring into your agent loop

javascript
// Inside your agent's skill execution loop:
async function executeSkill(skill, inputs, caller) {
  const before = beforeSkillCall(skill, inputs);
  const result = await callMCP(skill, inputs);   // your MCP call
  const receipt = await afterSkillCall(before, result, caller);
  return { result, receipt };
}
🔑 For production, use the API key issued at pilot onboarding. Pass it as Authorization: Bearer YOUR_KEY to tag traffic as source: 'live'. Without a key, traffic is tagged as source: 'demo' and excluded from compliance exports.

API Reference

POST /api/receipts/invoke

Call a skill and receive a signed execution receipt. Every invocation is recorded — including failures.

Request body

json
{
  "skill_name": "echo",           // required — skill/tool identifier
  "inputs": { "message": "hello" }, // required — skill inputs as JSON object
  "caller": "my-agent-v1"         // optional — caller identifier for audit filtering
}

Response

Response — 200 OK
{
  "receipt": {
    "id": 142,
    "skill_name": "echo",
    "timestamp": "2026-05-27T08:00:00.000Z",
    "inputs_hash": "a1b2c3d4e5f6…",
    "output_hash": "f6e5d4c3b2a1…",
    "signature": "xK8mP2nLqRvT4wYzAb3c…",
    "verification_status": "verified",
    "source": "live",
    "caller": "my-agent-v1"
  },
  "result": { "content": [{ "type": "text", "text": "..." }] }
}

Error responses

json
// 400 Bad Request — missing required fields
{ "error": "skill_name and inputs are required" }

// 502 Bad Gateway — MCP server unreachable
{ "error": "MCP server unavailable", "detail": "...", "hint": "..." }

// Error receipts are still stored (every attempt is auditable)
{ "receipt": { "id": 143, "skill_name": "...", "verification_status": "error" }, "result": { "status": "error", "message": "..." } }
GET /api/receipts

List receipts with optional filters. Results sorted by created_at DESC (newest first).

Query parameters

ParamTypeDescription
skill_namestringFilter by skill name (exact match)
sourcestringlive or demo
callerstringFilter by caller identifier
date_fromYYYY-MM-DDReceipts created on or after this date
date_toYYYY-MM-DDReceipts created on or before this date
limitintegerMax results, 1–200 (default: 50)
offsetintegerPagination offset (default: 0)
bash
# Live receipts for a specific skill, paginated
curl "https://axiom-ai-19.polsia.app/api/receipts?skill_name=echo&source=live&limit=20&offset=0"

# All receipts from a specific caller in May 2026
curl "https://axiom-ai-19.polsia.app/api/receipts?caller=my-agent-v1&date_from=2026-05-01&date_to=2026-05-31"
GET /api/receipts/:id/verify

Verify a receipt's integrity. Timing-safe HMAC check. No authentication required — anyone can verify any receipt by ID.

bash
curl https://axiom-ai-19.polsia.app/api/receipts/142/verify | jq .

Verification response

Response
{
  "receipt_id": 142,
  "skill_name": "echo",
  "timestamp": "2026-05-27T08:00:00.000Z",
  "source": "live",
  "caller": "my-agent-v1",
  "inputs_hash": "a1b2c3d4e5f6…",
  "output_hash": "f6e5d4c3b2a1…",
  "signature_valid": true,       // HMAC-SHA256 intact
  "verification_status": "verified",
  "verified": true               // signature valid AND not an error receipt
}
🔒 verified: false with signature_valid: true indicates a successful call that returned an error result — the signature is valid but the skill execution failed. Check verification_status for the full picture.
GET /api/receipts/invocations

List available skills on the configured MCP server. Useful for dynamic agent tool discovery.

bash
curl https://axiom-ai-19.polsia.app/api/receipts/invocations | jq .tools[].name

Receipt Schema

Every receipt is a tamper-evident cryptographic record. The fields below are the complete schema — compliance auditors will read this directly.

Field Type Description Example
id integer Auto-incrementing primary key. Use this for verification lookups. 142
skill_name string Tool/skill identifier as declared in the MCP manifest. "echo"
timestamp ISO 8601 UTC time of invocation initiation. Precision to milliseconds. Used as part of the signed payload — tampering changes the signature. "2026-05-27T08:00:00.000Z"
inputs_hash SHA-256 hex SHA-256 of canonical JSON of skill inputs. Keys sorted for determinism. Identical inputs always produce identical hash — verifiable offline. "a1b2c3d4e5f6..."
output_hash SHA-256 hex SHA-256 of canonical JSON of skill output. If output is a raw scalar, it's stringified first. "f6e5d4c3b2a1..."
signature base64 HMAC-SHA256 over skill_name|timestamp|inputs_hash|output_hash. Verifiable independently using the signing secret. "xK8mP2nLqRvT4wYzAb3c..."
verification_status enum "verified" — successful execution. "error" — execution failed (receipt still valid, but skill returned an error). "pending" — not yet verified. "verified"
source enum "live" — authenticated production traffic. "demo" — from the /receipts/demo UI, excluded from compliance exports. "live"
caller string | null Arbitrary caller identifier set at invocation time. Use for filtering by agent ID, user ID, or session. "my-agent-v1"
created_at ISO 8601 DB insert timestamp. For log correlation and pagination. "2026-05-27T08:00:00.123Z"
📋 The canonical schema is also in /spec/RECEIPT_SPEC.md. Open-source verifiers for Node.js and Python use this spec — compliance teams can run them directly against receipts pulled from the API.

Security Model

HMAC Signing

Receipts are signed with HMAC-SHA256 using a secret key. The signing payload is:

text
signing_payload = skill_name + "|" + timestamp + "|" + inputs_hash + "|" + output_hash
signature = base64(HMAC-SHA256(signing_payload, signing_secret))

The signing secret is derived from RECEIPT_SECRET environment variable. In production, set this to a cryptographically random 256-bit value. Rotation: generate a new secret, re-sign active receipts, deploy, then revoke the old secret.

Input/Output Hashing

Hashes use SHA-256 over canonical JSON. Keys are sorted alphabetically before serialization to guarantee determinism across runs and languages:

javascript
// Inputs are serialized with keys sorted — identical inputs always produce identical hash
const canonical = JSON.stringify(inputs, Object.keys(inputs).sort());
const inputsHash = crypto.createHash('sha256').update(canonical).digest('hex');

Timing-Safe Verification

Verification uses crypto.timingSafeEqual to prevent timing attacks. String comparison in constant time ensures attackers cannot use response latency to infer the correct signature byte-by-byte.

What Axiom Can't See

🚫
The signing secret never leaves your infrastructure.
Axiom receives the receipt payload (skill name, timestamp, hashes) but not the signing secret. You compute the signature client-side using your copy of the secret and send the result to Axiom for storage. For the highest assurance posture, run Axiom in self-hosted mode where the signing secret stays on your own servers.

Axiom cannot forge a valid receipt because it cannot compute the HMAC without your secret. Verification proves the receipt was created with the same secret used to verify — you control both ends of that chain.

Moving from Demo to Production

Before going live, work through this checklist. Each item is a real compliance requirement that enterprise procurement will ask about.

  1. Get a production API key. Demo keys tag traffic as source: 'demo', which is excluded from compliance exports. Retrieve your production key at /account/billing.
  2. Point at the production endpoint. Base URL: https://axiom-ai-19.polsia.app. Set AXIOM_BASE_URL in your agent's env so you can swap endpoints for testing.
  3. Configure receipt retention policy. Define how long receipts must be kept based on your regulatory requirements. By default, Axiom retains receipts indefinitely. Contact joe@getaifo.com for data retention SLA customization.
  4. Set up monitoring on receipt volume. Track GET /api/receipts?source=live volume. A spike in error receipts or a drop in total receipts is a sign something is wrong in the invocation path. Set alerts at 2× your baseline.
  5. Enable audit log export. Export receipts on a schedule using GET /api/receipts?source=live&date_from=...&date_to=.... Format: JSON Lines (\n-delimited) is recommended for bulk ingestion into your SIEM.
  6. Rotate the signing secret. Generate a production-strength secret (node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"). Store it in your secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.), not in source code.
  7. Test the full verify loop. Invoke a skill, fetch the receipt ID, call GET /api/receipts/:id/verify, confirm verified: true. Do this with your production key and in your deployment environment before the first compliance audit.

Also in the docs:

← Quickstart Verify a receipt Request pilot access