AXIOM

io.github.dragoscvPublished by GitHub account dragoscvv3.0.0Updated Oct 24, 2026

Transactional write gate for coding agents: plan, canonical manifest, checks, two-phase apply.

The publisher's own server: Axiom

Overview

AI-generated overview

AXIOM is a transactional write gate for coding agents: it plans, checks, previews and atomically applies file changes inside allowlisted repository roots…

What you can do
  • Plan and compile changes: validate plans, compile them into a canonical manifest bundle, parse .axm sources, diff manifests and snapshot the repo.
  • Check before writing: run set-level checks and pre-apply profiles, start long checks as pollable tasks, and dry-run an apply to see the diff.
  • Write through the gate: preview and then atomically apply multi-file writes or deletes once the manifest digest is confirmed, with locking and backups.
  • Recover and inspect: roll back an applied manifest, read status, lock holders and journal chain, and list allowed roots.
  • Verify trust: verify bundles and signatures against pinned Ed25519 keys in the root's trust file.
When to use it
Use it when an agent edits real repositories and you want every write to be previewed, checked and applied atomically with a rollback path, rather than letting the model write files directly. It also suits teams that need signed manifests, anti-rollback counters or an audit journal for agent-made changes.
Requirements
Runs locally over stdio via npx (Node.js), started with one or more absolute --root directories; there is no cwd or env fallback. An optional Streamable HTTP mode exists, with a bearer token required for non-loopback binds. Signing is done by the CLI with AXIOM_SIGNING_KEY or a key file, not by the server. External guards stay off unless enabled by flag and profile.
Before you install
The core tools write, delete and roll back files in the configured roots, so scope roots narrowly; apply requires echoing the manifest digest and refuses on drift. External guard commands execute local processes when enabled. AXIOM_SIGNING_KEY is an Ed25519 private key and should be kept out of the server and out of the repo. Non-loopback HTTP exposure needs a bearer token.

Installation

In SourceWeft

  1. Open AXIOM in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

@codai/axiom-mcp

MCP server (stdio or Streamable HTTP) and CLI for AXIOM v2 — the transactional write gate for coding agents: Plan → canonical ManifestBundle → set-level checks → hash-gated two-phase apply → journal. dist/cli.js (thin entry) + dist/cli-main.js (lazy-loaded engines; the MCP SDK v2 and zod are bundled into the mcp-lazy / http-lazy chunks); no runtime dependencies.

Install & run

sh
npx @codai/axiom-mcp mcp --root /abs/path/to/repo          # stdio MCP servernpx @codai/axiom-mcp mcp --root /abs/path/to/repo --http 127.0.0.1:3411   # Streamable HTTP at /mcpnpx @codai/axiom-mcp --help                                # CLI verbs

--root may repeat. Every tool root argument must equal or lie inside one of them; with exactly one root it is the default. There is no env-var or cwd fallback (ERR_ROOT_REQUIRED / ERR_ROOT_NOT_ALLOWED).

Protocol revisions — --wire 2026|2025|2026-only

Built on MCP TypeScript SDK v2 (@modelcontextprotocol/server 2.0.0). The server speaks the 2026-07-28 revision (no initialize, per-request _meta envelope, server/discover, ttlMs/cacheScope on list results) and the 2025-era revisions (initialize handshake, HTTP sessions) from the same entry — the SDK pins each stdio connection, or routes each HTTP request, to the era the client opened with. --wire 2026 (default) and --wire 2025 both serve both; --wire 2026-only refuses 2025 openings with the unsupported-protocol-version error. Clients on SDK v1 need no change. Details: docs/reference/mcp-tools.md.

VS Code — .vscode/mcp.json

json
{  "servers": {    "axiom": {      "type": "stdio",      "command": "npx",      "args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "${workspaceFolder}"]    }  }}

Streamable HTTP — --http <host:port>

sh
axiom mcp --root /abs/repo --http 127.0.0.1:3411            # loopback, no token neededaxiom mcp --root /abs/repo --http 0                          # random port; URL logged at info levelAXIOM_HTTP_TOKEN=$(openssl rand -hex 32) axiom mcp --root /abs/repo --http 0.0.0.0:3411 --log-level info
  • Endpoints: POST /mcp (2026-07-28 requests are served statelessly; a 2025 initialize opens a session and returns Mcp-Session-Id, which every later 2025 request must send), GET /mcp (standalone SSE stream, one per 2025 session), DELETE /mcp (close the session), GET /health → { ok, name, version } (unauthenticated). Anything else is 404 JSON.
  • Loopback by default. A non-loopback host refuses to start unless a bearer token is present in the env var named by --http-token-env <NAME> (default AXIOM_HTTP_TOKEN, ≥ 16 chars). Clients send Authorization: Bearer <token>; the compare is constant-time. A token is optional on loopback.
  • DNS-rebinding protection is on for loopback binds (Host must be <host>:<port>, localhost:<port> or 127.0.0.1:<port>; otherwise 403). Request bodies over 4 MiB are 413.
  • 2025 sessions idle for 30 minutes are evicted; each session (and each 2026 request) has its own server instance from one factory (roots, discovered sub-roots and guard settings are shared). The transport lives in dist/http-lazy.js, loaded only with --http, and is plain node:http — no express/hono at runtime.
  • VS Code: { "type": "http", "url": "http://127.0.0.1:3411/mcp" }; add "headers": { "Authorization": "Bearer ${input:axiom-token}" } when a token is set.

Conformance: packages/conformance runs @modelcontextprotocol/conformance server against this transport in CI with an expected-failures baseline (packages/conformance/baseline.yml).

Claude Desktop — claude_desktop_config.json

json
{  "mcpServers": {    "axiom": { "command": "npx", "args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "/abs/path/to/repo"] }  }}

Tools

Toolsets (3.0, D-43)

axiom mcp serves the core toolset by default — the six tools an agent needs to write through the gate: axiom_write, axiom_status, axiom_rollback, axiom_check, axiom_apply, axiom_apply_dry_run. Start the server with --tools full to also expose the plan / manifest / task plumbing below (every row not marked core). spec/tools.json and spec/codai-tools.json list every tool with "toolset": "core" | "full". Library users pass createServer(policy, { toolset: "full" }) / serverFactory(policy, { toolset }); parseToolset(value) maps the flag value (ERR_USAGE otherwise).

toolriskinputoutput
axiom_plan_validateREAD{ plan }{ ok, planDigest?, errors[] }
axiom_plan_compileACT{ plan, store?: inline|cas, root? }ManifestBundle (writes only under <root>/.axiom/ — CAS blobs and the stored manifest — when a root is given)
axiom_manifest_verifyREAD{ bundle, root? }{ ok, manifestDigest, canonical, signed, missing[], errors[], signatures?: { trustFile, keyids[], findings[], ok, code? } } — signatures only when root has .axiom/trust/keys.json; code is ERR_SIGNATURE_MISSING | ERR_SIGNATURE_INVALID when not ok
axiom_check coreREAD{ bundle, profile?, root? }CheckReport (verdict: pass|fail|error, preImage: verified|drifted|unverified)
axiom_check_startREAD{ bundle, profile?, root? }{ taskId, tool, status: "working", pollIntervalMs, ttlMs, elapsedMs } — same evaluation as axiom_check, returned immediately as a task so long guard.external suites (up to 15 min per guard) outlive the client's per-call timeout (S-406 / D-24)
axiom_task_getREAD{ taskId }descriptor + result: CheckReport once completed, or error: { code, message } once failed/cancelled; unknown/expired id → ERR_TASK_NOT_FOUND
axiom_task_cancelACT{ taskId }descriptor; kills every running guard tree, task ends cancelled with ERR_TASK_CANCELLED (idempotent on terminal tasks)
axiom_plan_beginACTPlan header: { name, intent, profile?, capabilities?, checks?, counter?, metadata? }{ sessionId, artifacts: 0, bytes: 0, limits: { maxArtifacts, maxBytes }, ttlMs } — opens a chunked plan session for Plans whose JSON would exceed the 4 MiB call cap
axiom_plan_addACT{ sessionId, artifacts[] } (each call ≤ 4 MiB)session descriptor; duplicate path across chunks → ERR_INVALID_PLAN, over budget (2000 artifacts / 64 MiB) or sealed → ERR_PLAN_SESSION_STATE
axiom_plan_sealACT{ sessionId, store?: inline|cas, root? }ManifestBundle — compiled by the same code path as axiom_plan_compile, so the digest equals a one-shot compile of the assembled Plan (property-tested); the session is consumed
axiom_apply_dry_run coreREAD{ bundle, root, profile? }ApplyResult{mode:"dry-run", diff}
axiom_apply coreSENSITIVE{ bundle, root, profile?, confirmDigest, mode?, branch?, commitMessage?, lockTimeoutMs? (0..600000), keepBackups? (0..1000) }ApplyResult — error.details carries { otherDigest, paths } for ERR_CONFLICT and { holder, waitedMs, queuePosition } for ERR_LOCKED
axiom_write coreSENSITIVE{ root, files[]: { path, content?, encoding?: utf8|base64, op?: write|delete, mode? }, intent?, profile?, confirmDigest?, lockTimeoutMs?, keepBackups? }One-tool write (2.5.0): without confirmDigest → status: "preview" (dry-run + checks, diff, manifestDigest, next; nothing outside .axiom/ touched); the same files + confirmDigest → atomic apply like axiom_apply (journal, one-step axiom_rollback). A target changed since the preview → ERR_CONFIRM_DIGEST_MISMATCH. Prefer it for multi-file or sensitive changes
axiom_rollback coreSENSITIVE{ root, manifestDigest }{ status:"rolled-back", phase, steps }
axiom_manifest_diffREAD{ a: bundle|"sha256:…", b }{ added[], removed[], changed[] }
axiom_axm_parseREAD{ source } (.axm text){ plan?, diagnostics: [{ severity, code, message, range: { start: {line, column}, end } }] }
axiom_roots_listREAD{}{ roots: [{ path, writable, hasGit }] }
axiom_status coreREAD{ root? }{ root, lock: { held, holder?: { pid, host?, digest?, since?, stale }, queue[], intents[] }, interrupted[], corrupt[], lastApplied?, chain: { ok, entries, head, firstBad?, missingJournals } } — same as axiom status --json; never takes the lock
axiom_repo_snapshotREAD{ root?, include?[], exclude?[], maxFiles? (20000, cap 50000), maxBytes? (64 MiB), respectGitignore? (true), withContentDigest? (true) }RepoSnapshot { snapshotDigest, body: { files: [{ path, bytes, sha256?, mode, kind }], truncated, counts } } — sorted, no timestamps/absolute paths; .git/, .axiom/ always skipped; symlinks recorded, never followed (docs/guides/snapshot.md)

Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) and an outputSchema; structuredContent is the full result, content[0].text a small summary (digest, verdict, counts, first 20 findings). Errors come back as isError: true with { code, message, path? } from the closed ERROR_CODES enum — a handler never throws. spec/tools.json is generated from the same registry (pnpm build:spec) and guarded by a parity test. spec/codai-tools.json is the same registry in codai's packages/agent-core/spec/tools-v2.json entry shape ({ name, risk, toolset, description, parameters }) — see docs/integration/codai.md.

Resources: axiom://manifest/{sha}, axiom://report/{sha}, axiom://applied/{sha}, axiom://profile/{name}, axiom://schema/{Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot}, axiom://emitters (template emitters loaded with --emitter <pkg> — empty by default since 3.0; install @codai/axiom-emitters-web next to the repo and pass --emitter @codai/axiom-emitters-web, see docs/guides/emitters.md), axiom://lock ({ roots: [{ root, held, holder?, queue[], intents[] }] } for every allowlisted root; read-only).

Trust model

  • Roots are realpath'd at startup, must be directories, and the set is frozen. Requested roots are realpath'd too (case-insensitive containment on Windows); anything outside is a hard error.
  • axiom_apply requires confirmDigest === bundle.manifestDigest — echo the digest you saw in dry-run. Pre-apply checks run against the profile (default default, or <root>/.axiom/profiles/<name>.json); a non-pass verdict aborts with ERR_CHECKS_FAILED before any write.
  • Payloads over 4 MiB are rejected up front (ERR_BUNDLE_TOO_LARGE).
  • Tasks and plan sessions live in the server process (shared by every connection/request the process serves; never on disk). A restart forgets them; finished tasks stay pollable for 10 min, idle sessions expire after 30 min; at most 8 tasks run concurrently (ERR_EBUSY beyond that). Stopping the server aborts every running task and kills its guard trees.
  • .axiom/lock makes apply single-writer per root; the journal makes it crash-safe and reversible.
  • stdout carries only JSON-RPC. Logs are JSON lines on stderr (--log-level error|warn|info|debug, default warn).
  • External guards (guard.external) are off unless the process is started with --allow-guards and the profile sets facts.allowGuards: true. Relative commands must live under <root>/scripts/; absolute ones must be listed exactly via --guard-allowlist <abs> (repeatable). Guards are spawned with an args array (never a shell), a scrubbed environment, a wall-clock timeout, and must print GuardOutput JSON — see docs/guides/checks.md.
  • Signed manifests (docs/guides/signing.md): a root can pin Ed25519 public keys in .axiom/trust/keys.json; a profile with manifest.requireSigned then refuses unsigned, tampered or untrusted bundles, and with antiRollback: true refuses any counter ≤ .axiom/trust/state.json#lastCounter. axiom_apply advances that state only on status: "applied". Private keys never enter the server: signing is axiom sign with AXIOM_SIGNING_KEY or --key-file.

CLI

axiom mcp     [--root <abs>]... [--allow-guards] [--guard-allowlist <abs>]... [--log-level warn]              [--http <host:port>] [--http-token-env AXIOM_HTTP_TOKEN] [--require-human]axiom init    [--root .] [--harness auto|copilot|claude|codex|vscode|cursor|windsurf|gemini|junie|cline] [--daemon]              [--profile-name <name>] [--force] [--upgrade] [--managed --out <dir>] [--json]              (.vscode/mcp.json + Pre/PostToolUse hooks + .axiom/profiles/<name>.json + .axiom/gate-profile.json + .gitignore; idempotent)axiom doctor  [--root .] [--json]               (exit 0 = no failing check, 2 = at least one fail; warnings exit 0)axiom stats   [--root .] [--since 7d] [--json]  (gate decisions, p50/p95, per harness; silent-gate alarms)axiom undo    --session <agent> [--root .] [--dry-run] [--force] [--since <iso>]   (restore one session's writes; exit 1 = conflicts)axiom import-guard <guard.ps1> [-o profile.json]   (PowerShell guard tables → gate profile "commands")axiom status  [--root .] [--json]               (lock holder/queue/intents, interrupted journals, last applied, chain)axiom log     [--root .] [--limit 20] [--json]  (journal chain, newest first)axiom compile <plan.json|plan.yaml|plan.axm> [-o out.json] [--store cas --root .] [--allow-net [--net-allow host[,host]]] [--allow-file]axiom verify  <bundle.json> [--root .]          (--root: also verify signatures against .axiom/trust/keys.json)axiom verify  --journal --root .                (journal hash chain; broken → exit 1 + code ERR_JOURNAL_CHAIN)axiom verify  <bundle.json> --tree <root> [--pre] [--attest out.intoto.json]   (tree matches manifest? docs/guides/verify-tree.md)axiom check   <bundle.json> --root . [--profile p] [--json | --format text|json|sarif] [--allow-guards] [--guard-allowlist <abs>]...axiom apply   <bundle.json> --root . [--dry-run] [--profile p] [--confirm <digest>] [--lock-timeout <ms>] [--keep-backups <n>]              [--pr [--branch b] [--message m]] [--allow-guards] [--guard-allowlist <abs>]...axiom rollback <digest> --root .axiom gc      --root . [--dry-run] [--older-than 30d] [--keep all-manifests|journal] [--undo-ttl 7d]   (CAS garbage collection; CLI only, no MCP tool)axiom diff    <a.json> <b.json>axiom schema  <Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot>axiom emitters [--json]axiom keygen  [--out <dir>] [--name <label>]     (ed25519; private key → <dir>/axiom-signing-<id>.key 0600, public entry → stdout)axiom sign    <bundle.json> [--key-file <path>] [-o out.json] [--root-id <id>]   (key from --key-file or $AXIOM_SIGNING_KEY; --root-id = root-bound envelope)axiom sign    <bundle.json> --keyless [--bound --root . | --root-id <id>] [-o out.json]              (Sigstore via ambient OIDC → bundle.keylessSignatures; no OIDC → exit 1 ERR_KEYLESS_UNAVAILABLE)axiom trust   add <pub.json> --root . | remove <keyid> --root . | list --root . | root-id [<id>|--clear] --root .axiom gate    --stdin [--post] [--format <harness>] [--root <dir>] [--profile <file>] [--fail-open] [--no-shell-scan] [--no-root-discovery]              [--track-reads] [--deny-exit 0|2] [--log-level warn]axiom serve   --hook --root <abs>... [--host 127.0.0.1] [--port 47861] [--token-env <NAME>] [--otel]axiom migrate v1 <manifest.json> [-o plan.json] [--profile default] [--cas <root>] [--content <dir>] [--overwrite]                                               (v1 manifest → v2 Plan, lazy chunk; exit 1 = migrated with warnings — docs/guides/migrate.md)axiom snapshot --root . [-o snap.json] [--include <glob>]... [--exclude <glob>]... [--max-files n] [--max-bytes n] [--no-gitignore] [--no-digest]axiom snapshot-diff <a.json> <b.json>          (RepoSnapshot → { added, removed, changed })

Exit codes: 0 ok · 1 verdict fail / apply failed / journal chain broken / keyless unavailable · 2 usage or error (doctor: 2 = a check failed). Non-mcp verbs print JSON to stdout (init, doctor, status, log, check, emitters: human lines unless --json).

ref sources ({ type: "ref", uri, digest }) are offline by default: a digest already in <root>/.axiom/cas resolves without network, anything else is ERR_NET_DISABLED. --allow-net fetches https: only (no redirects, 30 s timeout, 32 MiB cap), optionally restricted to --net-allow hosts (*.example.com wildcards), verifies the pinned digest and stores the blob in the CAS — a mismatch stores nothing (ERR_DIGEST_MISMATCH). apply never fetches. The MCP axiom_plan_compile tool has no network switch. See docs/reference/plan-format.md and docs/concepts/cas.md.

Hook mode — axiom gate --stdin

A PreToolUse hook for Claude Code, Copilot CLI and VS Code agent hooks. It reads one harness payload from stdin (both {tool_name, tool_input, cwd} and {toolName, toolArgs, cwd} casings; toolArgs may be a JSON string), extracts the write target(s) of Write|Edit|MultiEdit|NotebookEdit, create_file|replace_string_in_file|insert_edit_into_file|apply_patch|multi_replace_string_in_file|edit_notebook_file and generic write|edit, scans shell tools (Bash, run_in_terminal, …) for write primitives (>, >>, tee, rm, mv, cp, sed -i, git checkout|reset|clean, PowerShell Set-Content/Remove-Item, … — a heuristic, documented in docs/getting-started/hooks.md), and runs only the fast predicates: containment + RelPath rules (.., CON, NTFS ADS → ERR_CONTAINMENT / ERR_PATH_*), path.deny, path.allow, content.noSecrets and content.maxBytes on the new content when the payload carries it. Fail-closed (D-18): a non-answer is a deny.

outcomeexitstdoutstderr
allow / non-write, non-shell tool0——
deny0 (2 with --deny-exit 2)one object: hookSpecificOutput{…} (Claude) + flat permissionDecision/permissionDecisionReason (Copilot) + axiom{verdict, code, path, toolClass, standard:"owasp-acs/0.1"}AXIOM GATE DENY <code>: <reason> (<relpath>)
write tool with no recognised path keyas denydeny JSONAXIOM GATE DENY ERR_UNSUPPORTED_OP: …
malformed payload, stdin timeout (2 s), internal erroras deny (fail closed)deny JSONAXIOM GATE DENY ERR_INTERNAL: … (fail-closed; pass --fail-open to allow)
same, with --fail-open0—AXIOM GATE WARN: … — failing open (--fail-open)

A deny exits 0 since 2.4.1 (D-35): the npm axiom.ps1 shim on Windows rewrites 2 to 1, and VS Code treats 1 as a warning. Every JSON-aware harness honours the document on exit 0; use --deny-exit 2 for exit-code-only harnesses (Windsurf). Read-only tools are answered before the gate chunk loads. A target in a sibling worktree of the same repository (same git common dir) is judged against that worktree's root and gate profile instead of ERR_CONTAINMENT.

Root = payload cwd, walked up to the nearest .git / repository .axiom/ ancestor (never the home dir; --no-root-discovery disables), else --root, else the process cwd (the hook is the one place where cwd is acceptable: the harness spawns the hook in the project directory and owns that value). Relative targets stay relative to cwd. Profile = --profile <file> → <root>/.axiom/gate-profile.json → ~/.axiom/gate-profile.json → built-in { deny: [".git/**", ".axiom/**", "**/*.lock", "pnpm-lock.yaml", ".env", ".env.*", "**/node_modules/**"], noSecrets: true, pii: false }. Schema: { deny: string[], allow?: string[], noSecrets: boolean, pii: boolean, maxBytes?: number } (strict). pii: true additionally scans for personal data (cnp, email, phoneRo, card) — off by default since S-414 (maintainer e-mails in pyproject.toml denied real edits; see docs/guides/checks.md).

gate is a separate lazy chunk (dist/gate-lazy.js, no MCP SDK): in-process p95 ≈ 5 ms per payload, end-to-end ≈ 150–200 ms including node startup; check-gate-latency guards p95 ≤ 250 ms. Wiring for each harness is in docs/getting-started/hooks.md.

Source: packages/mcp/README.md at commit 8ddce4c

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

10
  1. v3.0.0LatestOct 19, 2026
  2. v2.7.0Oct 19, 2026
  3. v2.6.1Oct 14, 2026
  4. v2.5.2Oct 14, 2026
  5. v2.5.0Oct 14, 2026
  6. v2.4.1Oct 14, 2026
  7. v2.4.0Sep 27, 2026
  8. v2.3.1Sep 27, 2026
  9. v2.3.0Sep 23, 2026
  10. v2.2.1Sep 21, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

Other Axiom integrations