Skip to content
Chris edited this page Jun 6, 2026 · 4 revisions

Audit Trail

db-mcp includes a built-in JSONL audit trail with per-call token estimates, OAuth identity, timing, and outcome. Write/admin tools are always logged; read-scoped tools can be opted in via --audit-reads. Size-based log rotation prevents unbounded growth.


Enabling

CLI flags:

node dist/cli.js --audit-log /var/log/audit.jsonl --sqlite-native ./database.db

# Omit tool arguments from entries
node dist/cli.js --audit-log /var/log/audit.jsonl --audit-no-redact --sqlite-native ./database.db

Environment variables:

Variable Default Description
AUDIT_LOG File path or stderr to enable audit logging
AUDIT_REDACT true Redact tool arguments from entries (default: on)
AUDIT_READS false Log read-scoped tool calls (compact entries)

Docker:

docker run --rm -p 3000:3000 \
  -v ./data:/app/data \
  writenotenow/db-mcp:latest \
  --transport http --port 3000 --server-host 0.0.0.0 \
  --audit-log /var/log/audit.jsonl \
  --sqlite-native /app/data/database.db

Container Mode (stderr)

For Docker, Kubernetes, and orchestrated environments, route audit entries directly to the container log stream:

--audit-log stderr
# or
AUDIT_LOG=stderr

The orchestrator (Docker log driver, Kubernetes Fluentd/Fluentbit, CloudWatch Logs, etc.) captures stderr natively — no volume mounts or log rotation needed.


JSONL Format

Each line is a self-contained JSON object:

{
  "timestamp": "2026-05-30T12:00:00.000Z",
  "requestId": "75e9f60f-aad0-4519-81db-e0a79a57dd0e",
  "tool": "sqlite_write_query",
  "category": "write",
  "scope": "write",
  "user": "jane@example.com",
  "scopes": ["read", "write"],
  "durationMs": 3,
  "success": true,
  "args": {},
  "tokenEstimate": 42
}

Field Reference

Field Type Description
timestamp ISO 8601 string UTC timestamp of the invocation
requestId UUID string Correlation ID for the MCP request
tool string MCP tool name (e.g., sqlite_write_query)
category read/write/admin Derived from the tool's OAuth scope group
scope string Required OAuth scope for this tool
user string or null OAuth sub claim (null for reads and no-OAuth)
scopes string[] OAuth scopes granted to the caller
durationMs number Execution time in milliseconds
success boolean Whether the tool completed without error
error string? Error message (present only on failure)
args object? Tool input arguments (omitted when redacted)
tokenEstimate number? Estimated token count (~4 bytes per token)

Which Tools Are Logged?

Write and admin tools are always logged. Read-scoped tools are logged only when --audit-reads (or AUDIT_READS=true) is enabled.

Scope Groups / Tools Logged?
read core (read-only), json, text, stats, vector, geo, introspection ⚡ Opt-in (--audit-reads)
write migration, core write tools (write_query, upsert, batch_insert) ✅ Always
admin admin, codemode, core destructive (drop_table, truncate) ✅ Always

Read entries are compactargs, user, and scopes are omitted to keep per-entry size small.


Agent Access

The sqlite://audit resource exposes recent audit entries with a session summary to AI agents:

{
  "summary": {
    "totalTokenEstimate": 14200,
    "callCount": 47,
    "topToolsByTokens": [
      { "tool": "sqlite_read_query", "calls": 12, "tokens": 8400 },
      { "tool": "sqlite_execute_code", "calls": 3, "tokens": 3100 }
    ],
    "note": "Last 47 tool calls consumed ~14,200 tokens"
  },
  "entries": [{ "tool": "sqlite_read_query", "tokenEstimate": 420, "..." }],
  "total": 47
}

The summary block gives agents session-level token consumption visibility. When audit logging is disabled, the resource returns an empty array with a status message.


Log Rotation

The audit log rotates automatically when it exceeds the configured max size (default: 10MB). On rotation, the current file cascades down a 5-tiered rotation chain (.1 through .5). The oldest archive is deleted when a new rotation occurs.

For container deployments using --audit-log stderr, rotation is unnecessary — the orchestrator handles log management.


Backup Snapshots

Pre-mutation DDL snapshots automatically capture the state of database objects before destructive operations. This enables point-in-time recovery and schema drift detection.

Enabling

# Enable audit log + backup snapshots
node dist/cli.js --audit-log /var/log/audit.jsonl --audit-backup --sqlite-native ./database.db

# Also capture sample data
node dist/cli.js --audit-log /var/log/audit.jsonl --audit-backup --audit-backup-data --sqlite-native ./database.db
Variable Default Description
AUDIT_BACKUP false Enable pre-mutation snapshots
AUDIT_BACKUP_DATA false Include sample data rows in snapshots

Snapshotted Tools

Snapshots are triggered by destructive tools:

Tool Snapshot Type
sqlite_drop_table Table DDL (+data)
sqlite_drop_index Object DDL
sqlite_truncate Table DDL (+data)
sqlite_vacuum Table DDL
sqlite_drop_view View DDL
sqlite_drop_virtual_table Virtual table DDL
sqlite_migration_apply Migration marker
sqlite_migration_rollback Migration marker

Backup Management Tools

Five MCP tools provide agent access to backup snapshots:

Tool Description
sqlite_audit_list_backups List all available schema audit snapshots
sqlite_audit_get_backup Retrieve the contents of a specific snapshot
sqlite_audit_diff_backup Compare a snapshot against the live schema
sqlite_audit_restore_backup Restore schema from a snapshot (supports dry-run)
sqlite_audit_cleanup Enforce retention policy and remove expired snapshots
sqlite_audit_search Search and filter structured audit logs

Retention

Snapshots are cleaned up automatically based on age and count limits. The oldest snapshots exceeding either limit are deleted first.


Log Shipping

JSONL format is compatible with standard log aggregation platforms:

Platform Method
Datadog File tailing agent or stderr → Docker log driver
Grafana Loki Promtail file/journal source
Elastic/ELK Filebeat JSONL input
Splunk Universal Forwarder or HEC (JSON)
CloudWatch Logs awslogs Docker log driver (stderr mode)
Fluentd/Fluentbit tail plugin with JSON parser

For container deployments, --audit-log stderr is the recommended approach.


Audit Identity

When OAuth is enabled with audit logging, write/admin audit entries capture the authenticated user (claims.sub) and granted scopes — providing a forensic trail linking mutations to identities.


Related

Clone this wiki locally