New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@codai/axiom-mcp

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@codai/axiom-mcp

AXIOM v2 MCP server (stdio) and CLI: validate, compile, check and transactionally apply content-addressed manifest bundles inside an allowlisted set of roots

Source
npmnpm
Version
2.1.0
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

@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, SDK and zod bundled in); no runtime dependencies.

Install & run

npx @codai/axiom-mcp mcp --root /abs/path/to/repo          # stdio MCP server
npx @codai/axiom-mcp mcp --root /abs/path/to/repo --http 127.0.0.1:3411   # Streamable HTTP at /mcp
npx @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).

VS Code — .vscode/mcp.json

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

Streamable HTTP — --http <host:port>

axiom mcp --root /abs/repo --http 127.0.0.1:3411            # loopback, no token needed
axiom mcp --root /abs/repo --http 0                          # random port; URL logged at info level
AXIOM_HTTP_TOKEN=$(openssl rand -hex 32) axiom mcp --root /abs/repo --http 0.0.0.0:3411 --log-level info
  • Endpoints: POST /mcp (an initialize opens a session and returns Mcp-Session-Id; every later request must send it), GET /mcp (standalone SSE stream, one per 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.
  • Sessions idle for 30 minutes are evicted; each session has its own server instance (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

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

Tools

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 } } — signatures only when root has .axiom/trust/keys.json
axiom_checkREAD{ bundle, profile?, root? }CheckReport (verdict: pass|fail|error)
axiom_apply_dry_runREAD{ bundle, root, profile? }ApplyResult{mode:"dry-run", diff}
axiom_applySENSITIVE{ bundle, root, profile?, confirmDigest }ApplyResult
axiom_rollbackSENSITIVE{ 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_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/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, 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 available to axiom_plan_compile — web@2.0.0, see docs/emitters.md).

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).
  • .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/checks.md.
  • Signed manifests (docs/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]
axiom compile <plan.json> [-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 check   <bundle.json> --root . [--profile p] [--json] [--allow-guards] [--guard-allowlist <abs>]...
axiom apply   <bundle.json> --root . [--dry-run] [--profile p] [--confirm <digest>] [--allow-guards] [--guard-allowlist <abs>]...
axiom rollback <digest> --root .
axiom gc      --root . [--dry-run] [--older-than 30d] [--keep all-manifests|journal]   (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]   (key from --key-file or $AXIOM_SIGNING_KEY)
axiom trust   add <pub.json> --root . | remove <keyid> --root . | list --root .
axiom gate    --stdin [--root <dir>] [--profile <file>] [--strict] [--log-level warn]
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/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 · 2 usage or error. Non-mcp verbs print JSON to stdout.

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/plan-format.md and docs/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, 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.

outcomeexitstdoutstderr
allow / unknown tool0——
deny2{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"…"}}AXIOM GATE DENY <code>: <reason> (<relpath>)
malformed payload, stdin timeout (2 s), internal error0 (fail open)—AXIOM GATE WARN: …
same, with --strict2deny JSONAXIOM GATE DENY ERR_INTERNAL: …

Root = payload cwd, 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). 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 }. Schema: { deny: string[], allow?: string[], noSecrets: boolean, maxBytes?: number } (strict).

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/hooks.md.

Keywords

axiom

FAQs

Package last updated on 19 Sep 2026

Related posts