@codai/axiom-mcp 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -21
- package/dist/axm-lazy-LAnrfjUO.js +6284 -0
- package/dist/axm-lazy-Xin1nh7Q.js +6284 -0
- package/dist/axm-lazy.js +196 -7
- package/dist/cli-main.js +3661 -14919
- package/dist/dist-Bt9Wvd_P.js +6379 -0
- package/dist/dist-D4M2Lyji.js +6378 -0
- package/dist/gate-lazy.js +654 -43
- package/dist/gc-lazy.js +6885 -0
- package/dist/http-lazy.js +23527 -0
- package/dist/index.d.ts +141 -5
- package/dist/index.js +974 -50
- package/dist/lib-CqHwM4m_.js +3884 -0
- package/dist/mcp-lazy.js +23050 -0
- package/dist/migrate-lazy.js +6513 -0
- package/dist/verify-tree-lazy.js +6893 -0
- package/package.json +15 -8
- package/spec/codai-tools.json +348 -2
- package/spec/tools.json +1721 -72
package/README.md
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
# @codai/axiom-mcp
|
|
2
2
|
|
|
3
|
-
MCP server (stdio) and CLI for AXIOM v2 — the transactional write gate for coding agents:
|
|
3
|
+
MCP server (stdio or Streamable HTTP) and CLI for AXIOM v2 — the transactional write gate for coding agents:
|
|
4
4
|
`Plan` → canonical `ManifestBundle` → set-level checks → hash-gated two-phase `apply` → journal.
|
|
5
|
-
`dist/cli.js` (thin entry) + `dist/cli-main.js` (lazy-loaded engines
|
|
5
|
+
`dist/cli.js` (thin entry) + `dist/cli-main.js` (lazy-loaded engines; the MCP SDK v2 and zod are
|
|
6
|
+
bundled into the `mcp-lazy` / `http-lazy` chunks); no runtime dependencies.
|
|
6
7
|
|
|
7
8
|
## Install & run
|
|
8
9
|
|
|
9
10
|
```sh
|
|
10
11
|
npx @codai/axiom-mcp mcp --root /abs/path/to/repo # stdio MCP server
|
|
12
|
+
npx @codai/axiom-mcp mcp --root /abs/path/to/repo --http 127.0.0.1:3411 # Streamable HTTP at /mcp
|
|
11
13
|
npx @codai/axiom-mcp --help # CLI verbs
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
`--root` may repeat. Every tool `root` argument must equal or lie inside one of them; with exactly one
|
|
15
17
|
root it is the default. There is **no** env-var or `cwd` fallback (`ERR_ROOT_REQUIRED` / `ERR_ROOT_NOT_ALLOWED`).
|
|
16
18
|
|
|
19
|
+
### Protocol revisions — `--wire 2026|2025|2026-only`
|
|
20
|
+
|
|
21
|
+
Built on MCP TypeScript SDK **v2** (`@modelcontextprotocol/server` 2.0.0). The server speaks the
|
|
22
|
+
**2026-07-28** revision (no `initialize`, per-request `_meta` envelope, `server/discover`,
|
|
23
|
+
`ttlMs`/`cacheScope` on list results) **and** the 2025-era revisions (`initialize` handshake,
|
|
24
|
+
HTTP sessions) from the same entry — the SDK pins each stdio connection, or routes each HTTP
|
|
25
|
+
request, to the era the client opened with. `--wire 2026` (default) and `--wire 2025` both serve
|
|
26
|
+
both; `--wire 2026-only` refuses 2025 openings with the unsupported-protocol-version error. Clients
|
|
27
|
+
on SDK v1 need no change. Details: [docs/mcp_api.md](../../docs/mcp_api.md#protocol-revisions---wire-d-19).
|
|
28
|
+
|
|
17
29
|
### VS Code — `.vscode/mcp.json`
|
|
18
30
|
|
|
19
31
|
```json
|
|
@@ -28,6 +40,33 @@ root it is the default. There is **no** env-var or `cwd` fallback (`ERR_ROOT_REQ
|
|
|
28
40
|
}
|
|
29
41
|
```
|
|
30
42
|
|
|
43
|
+
### Streamable HTTP — `--http <host:port>`
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
axiom mcp --root /abs/repo --http 127.0.0.1:3411 # loopback, no token needed
|
|
47
|
+
axiom mcp --root /abs/repo --http 0 # random port; URL logged at info level
|
|
48
|
+
AXIOM_HTTP_TOKEN=$(openssl rand -hex 32) axiom mcp --root /abs/repo --http 0.0.0.0:3411 --log-level info
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Endpoints: `POST /mcp` (2026-07-28 requests are served statelessly; a 2025 `initialize` opens a
|
|
52
|
+
session and returns `Mcp-Session-Id`, which every later 2025 request must send), `GET /mcp`
|
|
53
|
+
(standalone SSE stream, one per 2025 session), `DELETE /mcp` (close the session), `GET /health` →
|
|
54
|
+
`{ ok, name, version }` (unauthenticated). Anything else is `404` JSON.
|
|
55
|
+
- **Loopback by default.** A non-loopback host **refuses to start** unless a bearer token is present in
|
|
56
|
+
the env var named by `--http-token-env <NAME>` (default `AXIOM_HTTP_TOKEN`, ≥ 16 chars). Clients send
|
|
57
|
+
`Authorization: Bearer <token>`; the compare is constant-time. A token is optional on loopback.
|
|
58
|
+
- DNS-rebinding protection is on for loopback binds (`Host` must be `<host>:<port>`, `localhost:<port>`
|
|
59
|
+
or `127.0.0.1:<port>`; otherwise `403`). Request bodies over 4 MiB are `413`.
|
|
60
|
+
- 2025 sessions idle for 30 minutes are evicted; each session (and each 2026 request) has its own
|
|
61
|
+
server instance from one factory (roots, discovered sub-roots and guard settings are shared). The
|
|
62
|
+
transport lives in `dist/http-lazy.js`, loaded only with `--http`, and is plain `node:http` — no
|
|
63
|
+
express/hono at runtime.
|
|
64
|
+
- VS Code: `{ "type": "http", "url": "http://127.0.0.1:3411/mcp" }`; add
|
|
65
|
+
`"headers": { "Authorization": "Bearer ${input:axiom-token}" }` when a token is set.
|
|
66
|
+
|
|
67
|
+
Conformance: `packages/conformance` runs `@modelcontextprotocol/conformance server` against this transport
|
|
68
|
+
in CI with an expected-failures baseline (`packages/conformance/baseline.yml`).
|
|
69
|
+
|
|
31
70
|
### Claude Desktop — `claude_desktop_config.json`
|
|
32
71
|
|
|
33
72
|
```json
|
|
@@ -44,14 +83,21 @@ root it is the default. There is **no** env-var or `cwd` fallback (`ERR_ROOT_REQ
|
|
|
44
83
|
|---|---|---|---|
|
|
45
84
|
| `axiom_plan_validate` | READ | `{ plan }` | `{ ok, planDigest?, errors[] }` |
|
|
46
85
|
| `axiom_plan_compile` | ACT | `{ plan, store?: inline\|cas, root? }` | `ManifestBundle` (writes only under `<root>/.axiom/` — CAS blobs and the stored manifest — when a root is given) |
|
|
47
|
-
| `axiom_manifest_verify` | READ | `{ bundle }` | `{ ok, manifestDigest, canonical, signed, missing[], errors[] }` |
|
|
48
|
-
| `axiom_check` | READ | `{ bundle, profile?, root? }` | `CheckReport` (`verdict: pass\|fail\|error`) |
|
|
86
|
+
| `axiom_manifest_verify` | READ | `{ 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` |
|
|
87
|
+
| `axiom_check` | READ | `{ bundle, profile?, root? }` | `CheckReport` (`verdict: pass\|fail\|error`, `preImage: verified\|drifted\|unverified`) |
|
|
88
|
+
| `axiom_check_start` | READ | `{ 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) |
|
|
89
|
+
| `axiom_task_get` | READ | `{ taskId }` | descriptor + `result: CheckReport` once `completed`, or `error: { code, message }` once `failed`/`cancelled`; unknown/expired id → `ERR_TASK_NOT_FOUND` |
|
|
90
|
+
| `axiom_task_cancel` | ACT | `{ taskId }` | descriptor; kills every running guard tree, task ends `cancelled` with `ERR_TASK_CANCELLED` (idempotent on terminal tasks) |
|
|
91
|
+
| `axiom_plan_begin` | ACT | Plan 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 |
|
|
92
|
+
| `axiom_plan_add` | ACT | `{ 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` |
|
|
93
|
+
| `axiom_plan_seal` | ACT | `{ 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 |
|
|
49
94
|
| `axiom_apply_dry_run` | READ | `{ bundle, root, profile? }` | `ApplyResult{mode:"dry-run", diff}` |
|
|
50
95
|
| `axiom_apply` | SENSITIVE | `{ bundle, root, profile?, confirmDigest }` | `ApplyResult` |
|
|
51
96
|
| `axiom_rollback` | SENSITIVE | `{ root, manifestDigest }` | `{ status:"rolled-back", phase, steps }` |
|
|
52
97
|
| `axiom_manifest_diff` | READ | `{ a: bundle\|"sha256:…", b }` | `{ added[], removed[], changed[] }` |
|
|
53
98
|
| `axiom_axm_parse` | READ | `{ source }` (`.axm` text) | `{ plan?, diagnostics: [{ severity, code, message, range: { start: {line, column}, end } }] }` |
|
|
54
99
|
| `axiom_roots_list` | READ | `{}` | `{ roots: [{ path, writable, hasGit }] }` |
|
|
100
|
+
| `axiom_repo_snapshot` | READ | `{ 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`) |
|
|
55
101
|
|
|
56
102
|
Every tool carries MCP `annotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`)
|
|
57
103
|
and an `outputSchema`; `structuredContent` is the full result, `content[0].text` a small summary (digest,
|
|
@@ -62,7 +108,8 @@ codai's `packages/agent-core/spec/tools-v2.json` entry shape (`{ name, risk, des
|
|
|
62
108
|
see `docs/integration/codai.md`.
|
|
63
109
|
|
|
64
110
|
Resources: `axiom://manifest/{sha}`, `axiom://report/{sha}`, `axiom://applied/{sha}`,
|
|
65
|
-
`axiom://profile/{name}`, `axiom://schema/{Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal}
|
|
111
|
+
`axiom://profile/{name}`, `axiom://schema/{Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot}`,
|
|
112
|
+
`axiom://emitters` (template emitters available to `axiom_plan_compile` — `web@2.0.0`, see `docs/emitters.md`).
|
|
66
113
|
|
|
67
114
|
## Trust model
|
|
68
115
|
|
|
@@ -72,6 +119,10 @@ Resources: `axiom://manifest/{sha}`, `axiom://report/{sha}`, `axiom://applied/{s
|
|
|
72
119
|
Pre-apply checks run against the profile (default `default`, or `<root>/.axiom/profiles/<name>.json`);
|
|
73
120
|
a non-`pass` verdict aborts with `ERR_CHECKS_FAILED` before any write.
|
|
74
121
|
- Payloads over 4 MiB are rejected up front (`ERR_BUNDLE_TOO_LARGE`).
|
|
122
|
+
- Tasks and plan sessions live in the server **process** (shared by every connection/request the
|
|
123
|
+
process serves; never on disk). A restart forgets them; finished tasks stay pollable for 10 min, idle
|
|
124
|
+
sessions expire after 30 min; at most 8 tasks run concurrently (`ERR_EBUSY` beyond that). Stopping
|
|
125
|
+
the server aborts every running task and kills its guard trees.
|
|
75
126
|
- `.axiom/lock` makes apply single-writer per root; the journal makes it crash-safe and reversible.
|
|
76
127
|
- stdout carries only JSON-RPC. Logs are JSON lines on stderr (`--log-level error|warn|info|debug`, default `warn`).
|
|
77
128
|
- External guards (`guard.external`) are **off** unless the process is started with `--allow-guards`
|
|
@@ -79,45 +130,77 @@ Resources: `axiom://manifest/{sha}`, `axiom://report/{sha}`, `axiom://applied/{s
|
|
|
79
130
|
absolute ones must be listed exactly via `--guard-allowlist <abs>` (repeatable). Guards are spawned
|
|
80
131
|
with an args array (never a shell), a scrubbed environment, a wall-clock timeout, and must print
|
|
81
132
|
`GuardOutput` JSON — see `docs/checks.md`.
|
|
133
|
+
- **Signed manifests** (`docs/signing.md`): a root can pin Ed25519 public keys in
|
|
134
|
+
`.axiom/trust/keys.json`; a profile with `manifest.requireSigned` then refuses unsigned, tampered or
|
|
135
|
+
untrusted bundles, and with `antiRollback: true` refuses any `counter ≤ .axiom/trust/state.json#lastCounter`.
|
|
136
|
+
`axiom_apply` advances that state only on `status: "applied"`. Private keys never enter the server:
|
|
137
|
+
signing is `axiom sign` with `AXIOM_SIGNING_KEY` or `--key-file`.
|
|
82
138
|
|
|
83
139
|
## CLI
|
|
84
140
|
|
|
85
141
|
```
|
|
86
142
|
axiom mcp [--root <abs>]... [--allow-guards] [--guard-allowlist <abs>]... [--log-level warn]
|
|
87
|
-
|
|
88
|
-
axiom
|
|
143
|
+
[--http <host:port>] [--http-token-env AXIOM_HTTP_TOKEN]
|
|
144
|
+
axiom compile <plan.json> [-o out.json] [--store cas --root .] [--allow-net [--net-allow host[,host]]] [--allow-file]
|
|
145
|
+
axiom verify <bundle.json> [--root .] (--root: also verify signatures against .axiom/trust/keys.json)
|
|
146
|
+
axiom verify <bundle.json> --tree <root> [--pre] [--attest out.intoto.json] (tree matches manifest? docs/verify-tree.md)
|
|
89
147
|
axiom check <bundle.json> --root . [--profile p] [--json] [--allow-guards] [--guard-allowlist <abs>]...
|
|
90
148
|
axiom apply <bundle.json> --root . [--dry-run] [--profile p] [--confirm <digest>] [--allow-guards] [--guard-allowlist <abs>]...
|
|
91
149
|
axiom rollback <digest> --root .
|
|
150
|
+
axiom gc --root . [--dry-run] [--older-than 30d] [--keep all-manifests|journal] (CAS garbage collection; CLI only, no MCP tool)
|
|
92
151
|
axiom diff <a.json> <b.json>
|
|
93
|
-
axiom schema <Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal>
|
|
94
|
-
axiom
|
|
152
|
+
axiom schema <Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot>
|
|
153
|
+
axiom emitters [--json]
|
|
154
|
+
axiom keygen [--out <dir>] [--name <label>] (ed25519; private key → <dir>/axiom-signing-<id>.key 0600, public entry → stdout)
|
|
155
|
+
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)
|
|
156
|
+
axiom trust add <pub.json> --root . | remove <keyid> --root . | list --root . | root-id [<id>|--clear] --root .
|
|
157
|
+
axiom gate --stdin [--root <dir>] [--profile <file>] [--fail-open] [--no-shell-scan] [--no-root-discovery] [--log-level warn]
|
|
158
|
+
axiom migrate v1 <manifest.json> [-o plan.json] [--profile default] [--cas <root>] [--content <dir>] [--overwrite]
|
|
159
|
+
(v1 manifest → v2 Plan, lazy chunk; exit 1 = migrated with warnings — docs/migrate.md)
|
|
160
|
+
axiom snapshot --root . [-o snap.json] [--include <glob>]... [--exclude <glob>]... [--max-files n] [--max-bytes n] [--no-gitignore] [--no-digest]
|
|
161
|
+
axiom snapshot-diff <a.json> <b.json> (RepoSnapshot → { added, removed, changed })
|
|
95
162
|
```
|
|
96
163
|
|
|
97
164
|
Exit codes: `0` ok · `1` verdict fail / apply failed · `2` usage or error. Non-`mcp` verbs print JSON to stdout.
|
|
98
165
|
|
|
166
|
+
`ref` sources (`{ type: "ref", uri, digest }`) are offline by default: a digest already in
|
|
167
|
+
`<root>/.axiom/cas` resolves without network, anything else is `ERR_NET_DISABLED`. `--allow-net`
|
|
168
|
+
fetches `https:` only (no redirects, 30 s timeout, 32 MiB cap), optionally restricted to
|
|
169
|
+
`--net-allow` hosts (`*.example.com` wildcards), verifies the pinned digest and stores the blob in
|
|
170
|
+
the CAS — a mismatch stores nothing (`ERR_DIGEST_MISMATCH`). `apply` never fetches. The MCP
|
|
171
|
+
`axiom_plan_compile` tool has no network switch. See [docs/plan-format.md](../../docs/plan-format.md#ref-sources)
|
|
172
|
+
and [docs/cas.md](../../docs/cas.md).
|
|
173
|
+
|
|
99
174
|
## Hook mode — `axiom gate --stdin`
|
|
100
175
|
|
|
101
176
|
A PreToolUse hook for Claude Code, Copilot CLI and VS Code agent hooks. It reads **one** harness
|
|
102
177
|
payload from stdin (both `{tool_name, tool_input, cwd}` and `{toolName, toolArgs, cwd}` casings;
|
|
103
178
|
`toolArgs` may be a JSON string), extracts the write target(s) of `Write|Edit|MultiEdit|NotebookEdit`,
|
|
104
179
|
`create_file|replace_string_in_file|insert_edit_into_file|apply_patch|multi_replace_string_in_file|edit_notebook_file`
|
|
105
|
-
and generic `write|edit`,
|
|
106
|
-
(
|
|
107
|
-
`
|
|
180
|
+
and generic `write|edit`, scans **shell** tools (`Bash`, `run_in_terminal`, …) for write
|
|
181
|
+
primitives (`>`, `>>`, `tee`, `rm`, `mv`, `cp`, `sed -i`, `git checkout|reset|clean`, PowerShell
|
|
182
|
+
`Set-Content`/`Remove-Item`, … — a heuristic, documented in docs/hooks.md), and runs **only** the
|
|
183
|
+
fast predicates: containment + `RelPath` rules (`..`, `CON`, NTFS ADS → `ERR_CONTAINMENT` /
|
|
184
|
+
`ERR_PATH_*`), `path.deny`, `path.allow`, `content.noSecrets` and `content.maxBytes` on the new
|
|
185
|
+
content when the payload carries it. **Fail-closed** (D-18): a non-answer is a deny.
|
|
108
186
|
|
|
109
187
|
| outcome | exit | stdout | stderr |
|
|
110
188
|
|---|---|---|---|
|
|
111
|
-
| allow /
|
|
112
|
-
| deny | `2` | `
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
189
|
+
| allow / non-write, non-shell tool | `0` | — | — |
|
|
190
|
+
| deny | `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>)` |
|
|
191
|
+
| write tool with no recognised path key | `2` | deny JSON | `AXIOM GATE DENY ERR_UNSUPPORTED_OP: …` |
|
|
192
|
+
| malformed payload, stdin timeout (2 s), internal error | `2` (**fail closed**) | deny JSON | `AXIOM GATE DENY ERR_INTERNAL: … (fail-closed; pass --fail-open to allow)` |
|
|
193
|
+
| same, with `--fail-open` | `0` | — | `AXIOM GATE WARN: … — failing open (--fail-open)` |
|
|
194
|
+
|
|
195
|
+
Root = payload `cwd`, walked up to the nearest `.git` / repository `.axiom/` ancestor (never the
|
|
196
|
+
home dir; `--no-root-discovery` disables), else `--root`, else the process cwd (the hook is the
|
|
197
|
+
one place where cwd is acceptable: the harness spawns the hook in the project directory and owns
|
|
198
|
+
that value). Relative targets stay relative to `cwd`.
|
|
118
199
|
Profile = `--profile <file>` → `<root>/.axiom/gate-profile.json` → `~/.axiom/gate-profile.json` →
|
|
119
|
-
built-in `{ deny: [".git/**", ".axiom/**", "**/*.lock", "pnpm-lock.yaml", ".env", ".env.*", "**/node_modules/**"], noSecrets: true }`.
|
|
120
|
-
Schema: `{ deny: string[], allow?: string[], noSecrets: boolean, maxBytes?: number }` (strict).
|
|
200
|
+
built-in `{ deny: [".git/**", ".axiom/**", "**/*.lock", "pnpm-lock.yaml", ".env", ".env.*", "**/node_modules/**"], noSecrets: true, pii: false }`.
|
|
201
|
+
Schema: `{ deny: string[], allow?: string[], noSecrets: boolean, pii: boolean, maxBytes?: number }` (strict).
|
|
202
|
+
`pii: true` additionally scans for personal data (`cnp`, `email`, `phoneRo`, `card`) — off by default
|
|
203
|
+
since S-414 (maintainer e-mails in `pyproject.toml` denied real edits; see `docs/checks.md`).
|
|
121
204
|
|
|
122
205
|
`gate` is a separate lazy chunk (`dist/gate-lazy.js`, no MCP SDK): in-process p95 ≈ 5 ms per payload,
|
|
123
206
|
end-to-end ≈ 150–200 ms including node startup; `check-gate-latency` guards p95 ≤ 250 ms.
|