haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
---
name: haematite
description: Use typed Haematite tools to describe stores, query records, read history, and prepare and review branch changes.
---

Use the connected Haematite MCP server. The installed bundle is a plugin and marketplace for both Claude Code (`/plugin marketplace add <bundle>`; `/plugin install haematite@haematite`) and Codex (`codex plugin marketplace add <bundle>`; `codex plugin add haematite@haematite`). An installed bundle starts its own `bin/haematite` over stdio with its bundled `serve.json`; its plugin has no `userConfig` prompt. In Codex, the repository plugin starts the server through `scripts/mcp.sh`, using `HAEMATITE_CONFIG` or `~/.config/haematite/serve.json` for the running service's configuration and `HAEMATITE_BIN` or `haematite` on PATH for the binary.

The bundled configuration is a snapshot. Later changes to `socket` or `data_dir` require a new bundle directory. Repeating installation at the same directory with changed inputs refuses `bundle_conflict`; it does not rewrite the installed snapshot.

Read the connected server's `tools/list` definitions before constructing arguments. Each tool's `inputSchema` and `outputSchema` is the contract; validate nested values against those definitions and preserve their scalar representation. Do not infer fields from a tool's purpose or manufacture a caller identity.

Describe the selected store before choosing a collection or relationship. Keep reads tied to the captured version. {{query_continuation}} Report a partial answer as partial. All read tools take `after`; describe and review use a map of per-part cursors. Row limits are nonzero u32 values. `page.bytes` bounds the complete structured payload; the MCP envelope and duplicate text representation are outside that bound. Describe counts are complete values, with no cursor or partial wrapper. Preserve structured results and named refusals, without rounding large numbers.

Prepare changes on a branch and review their changes, conflicts and dangling endpoints before merging. `merge_branch` merges `from` into `branch` only at the `reviewed` version that `review_branch` read and only while `branch` is still at `expected`; any other version is refused, never substituted. Merge only after the review's conflicts and dangling findings are read to their end. Preserve request identifiers and expected versions. If a mutation reports an unknown outcome, stop mutations and report the refusal and identifier for reconciliation; do not retry it as a new request.

For `create_branch`, input `branch` names the source branch, and `expected` is its current head for the mutation guard. Input `name` names the new destination branch; `from` is the retained source version from which that destination forks. The receipt's `branch` names the destination, `version` is the destination's version, and `request_id` echoes the mutation identifier for reconciliation. The current source head and the retained fork version have separate roles.

read_history returns retained branch snapshots: branch, version and shard roots. Attribution is not stored by this service; history does not invent a caller, request identifier, change or timestamp.

review_branch returns one page for each part. Keep the captured selection version when continuing a part; pass its `next_after` back in `after` under that part's name. Native review begins at the current branch head. A requested version or target that differs between parts is refused by name, never substituted or automatically retried. Track completed parts separately and never add a repeated first page to a partial total.

The review counts page carries partial changes and conflicts. It makes no dangling count or safety claim. Read the dangling findings through their own cursor. Preserve each finding's state, missing_end, removed_by, relation, endpoints and choice_token; depends_on_choice does not mean a settled dangling endpoint. Differences and preparation lines use the same value form: `{"kind":"absent"}` for no value, or `{"kind":"value","value":<JSON>}` for a value, including JSON null. Preparation lines keep ancestor, target and source, plus their state and choice token. The page can include source_only and same_on_both as well as conflict.

## Network connection

Run the foreground listener with `haematite mcp-http --config /absolute/service.json`.
The existing service must already run at the configured absolute socket. Add an
explicit `mcp_http` object to that service configuration. Every field it accepts
is shown; any other field refuses startup:

```json
{
  "listen": "127.0.0.1:9041",
  "allowed_origins": ["https://agent.example"],
  "path": "/mcp",
  "resource_metadata_url": "https://identity.example/.well-known/oauth-protected-resource",
  "max_connections": 64,
  "header_timeout_ms": 2000,
  "body_timeout_ms": 5000
}
```

`listen` must be a loopback address; expose it through a TLS proxy that
preserves Authorization and Origin. No proxy, certificate or service is
installed by this command. `path` is the one request path answered (`/mcp`
unless set), so two listeners behind one hostname can each own a path.
`allowed_origins` lists canonical origins without paths; an Origin, when
present, must be listed. `resource_metadata_url` is optional and is named in
the challenge of every 401; no metadata endpoint is served. The other values
shown are the defaults.

The listener serves POST on its path with JSON responses and accepts
notifications with HTTP 202. GET is refused with HTTP 405; it does not open an
event stream or mint sessions. Send Content-Type application/json, Accept
application/json and text/event-stream, and the negotiated MCP-Protocol-Version.

Who may come in is not a listener setting. Every request carries the caller's
own Lys pass for haematite as its bearer, and the listener presents that pass to
the store, which judges it and every act under it by that caller's grants. A
request with no bearer refuses 401 `mcp_sign_in_required`. A pass the store
refuses as missing, expired, not yet valid, for the wrong audience or issuer,
under an unpublished key, or badly signed or encoded refuses 401
`mcp_pass_refused`: ask Lys for a fresh pass. A store that cannot be reached,
or refuses for another reason, answers 503 `mcp_store_unavailable`.

Connections, including unfinished headers, are bounded by `max_connections`
(`mcp_http_connection_busy`); calls to the store are bounded by the service
file's `max_clients` (`mcp_http_busy`). The header and body deadlines are
independent, and response writes use the body deadline. The service file's
existing `timeout_ms` still bounds each native transport phase; a tool can make
several native operations, so these knobs do not claim a single total deadline
for a whole tool call.

Each answered request half-closes the write side after the complete answer, then
reads and discards whatever the peer still sends for up to 1 s before closing
(`mcp_http_linger_timeout` in the log when the peer outlasts it). This bounds the
connection slot held by a peer that leaves its write side open. Connection-cap,
socket and spawn refusals on the accept thread write, half-close and drop without
a linger read, so a peer's delayed close cannot hold that loop.