# bantamkit-mcp [Health: Active]

**Category:** 🧠 Knowledge & Memory  
**Repository:** https://github.com/Ink01101011/bantamkit  
**GitHub Stars:** 2  
**npm Downloads (last month):** 1605  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/bantamkit-mcp

## Description
Memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop: per-person memory, JSON Schema validation, shift-work tools. Pure Node; install once with npx, run offline.

## Tools
Capabilities this server exposes over MCP:

- **memory_save** — Save ONE durable fact to persistent memory — a preference the user stated, a decision, the fix that worked — so a future session can recall it. Use it only for facts a future session needs; never for anything derivable from the repo, git history or docs, nor for one-off state of this conversation. To find a fact use memory_recall; to change one, save again under its EXACT name, which replaces it in place; to free room call memory_compact. Refused with nothing written, each with its own sentence: a description too close to an existing fact's (the reply names that fact — save under that SAME name or skip, never rename to force a copy), a spelling that normalises onto an existing name (`Deploy Command` onto `deploy-command`), and a save that would push the always-loaded index over its byte budget (the reply names the remedy). Every save lands in the writable project store (.bantamkit/memory), never in the profile or a read-only grant, and is appended to the usage ledger. Optional outcome (error|success) tags a lesson and status: done closes a project fact; nothing here archives — that is the operator's bantamkit-memory candidates/merge.
- **memory_recall** — Search persistent memory for facts a past session saved. Call it BEFORE starting a task that resembles past work, and when the user refers to something decided earlier; use memory_save to add a fact, and memory_compact or memory_dream only to tidy the store — this tool never changes what a fact says. Reads every layer in order — the project store, any read-only grants in .bantamkit/config.yaml, then the machine-wide profile — and returns the top word matches, never fewer than the store default of 3 when that many match; a query that is exactly a fact's name returns that fact alone. Pass `file` instead of `query` to see the facts linked to a file. Why it is not read-only: every hit is appended to the usage ledger (~/.bantamkit/usage/<store-id>.jsonl) as a recall event — the staleness order memory_compact archives by — and a project fact file still in the 0.35.x format is rewritten once into the current one; both are best-effort and never fail a recall. A fact file that cannot be read is named in the reply, never silently skipped. Pass outcome: error to see only recorded failures.
- **validate_json** — Validate candidate output text against a JSON Schema and say what is wrong. Answers {valid, feedback}: when invalid, hand `feedback` back to the model and retry — it says where the document fails and quotes the offending value, clipped to 200 characters; when valid, `feedback` is null. Use it on any JSON a model produced before acting on it; for a shift-work checkpoint call shiftwork_status or shiftwork_clock_in instead, which validate against the shipped checkpoint schema themselves. `output` may be a bare JSON value; otherwise the first fenced code block, else the first object or array in the text, is what gets validated. `$schema` selects draft-04, draft-06, draft-07, 2019-09 or 2020-12 (the default). `$ref` resolves only inside the one schema given (`#`, `#/...`, `#name`): a remote reference or an embedded `$id` is refused with its own sentence, never fetched. `pattern` is Python re syntax without inline flags, look-behind, backreferences, atomic, possessive or conditional groups or \N{...}, and all of a document's patterns share 500 ms: past that the answer is one refusal sentence naming the pattern that ran out, not a partial validation. Reads nothing but its two arguments and writes nothing.
- **shiftwork_clock_in** — Shift-work clock-in: validate the checkpoint against the checkpoint schema and return the brief of ONE unit — {unit, role, invariants, handoff, do_not, files} — to hand to the agent you spawn, verbatim. Call it once per unit, right before spawning; use shiftwork_plan first to see the `ready` batch when several units are to run at once, and shiftwork_clock_out, not this tool, when a unit finishes. The unit briefed is plan.cursor, or `unit_id` when given; either way it must be in shiftwork_plan's `ready` — a done or dropped cursor, a cursor whose dependencies have not run, or a `unit_id` naming no unit answers {"result": "error", "reason": "unit <id> is not ready; ready is <a, b>"}, and the batch view's own refusals (cycle, unknown dependency, unreadable checkpoint) pass through verbatim. A ready unit whose `touches` shares a path with a unit briefed and not yet clocked out is refused too, before any brief: `unit <id> touches <path>, which <other> holds until its clock-out`. A checkpoint whose `reviews` break review independence is refused with shiftwork_plan's sentence. With job.budget declared, a brief that does not fit is refused: `budget <dim> spent: <n> of <limit>; raise job.budget or stop the job`. The other outcomes, never exceptions: `escalate` when handoff.open_questions is non-empty (stop and ask the user) or when the cursor names no unit, `success` when every unit is done or dropped, `error` when the checkpoint fails validation. It never moves plan.cursor; clock_out does. What it WRITES: on result=brief, and only then, one line {event: brief, ts, unit, role} appended to <checkpoint>.log.jsonl — best-effort, so a log it cannot write costs the brief nothing. That line is what later lets clock_out accept the unit while the cursor is elsewhere and sets `briefed` on its accounting line. The checkpoint file itself is never modified. See docs/shiftwork.md.
- **shiftwork_clock_out** — Shift-work clock-out: record that a unit finished — set its status, advance plan.cursor, merge `handoff_patch` into handoff, push `history_entry` onto the 5-entry history ring, and append one accounting line to <checkpoint>.log.jsonl. Call it once per unit after its agent returns: shiftwork_clock_in briefs, this tool closes, and shiftwork_status reads what both wrote. The whole mutated document is validated against the checkpoint schema BEFORE one atomic write, so a failure writes nothing and answers {"result": "error", "reason": ...}; nothing is ever deleted from the checkpoint. `unit_id` must be plan.cursor — which needs no brief and clocks out with briefed=false — or a unit clock_in briefed since its own last clock-out; any other id is refused with `unit X is not the cursor unit Y`. `handoff_patch` and `history_entry` are both required arguments: pass `{}` to leave handoff unchanged, and give history_entry at least `unit` and `outcome`. plan.cursor then moves to the head of the ready batch recomputed on the new document — plan.units order over the remaining non-terminal units when the graph cannot batch, `unit_id` when none remain. The accounting line carries unit, role, status, ts and `briefed` (written by the runtime, never taken from you: true only when a brief was issued since this unit's LAST clock-out) plus your `accounting` fields when given. `follow_ups` appends the work it discovered as todo units. `agreements` adopts or retires a working agreement every later brief carries. Each clock-out also appends {status, outcome, notes?, produced?} to the unit's attempts (newest 3 kept). With job.budget declared, a line reporting 0 tokens is refused unless it carries `estimate: true`. When job.roles names this unit's role, `accounting.model` must be on that role's list, spelled exactly, or nothing is written — see the `model` property.
- **shiftwork_status** — Shift-work status: where ONE checkpoint file stands — units counted by status, the unit plan.cursor points at, how many handoff.open_questions are waiting, and the last history entry. Call it first when resuming a job or answering 'where is job X'; it is the cheap read before any clock-in. It does not say WHICH units may run now — that is shiftwork_plan, the dependency batches — and it says nothing about the server itself, which is bantamkit_status. Every fact is read from the checkpoint on this call, nothing is cached, and a checkpoint that is missing, not valid JSON or failing the checkpoint schema answers {"result": "error", "reason": "<sentence>"} instead of a partial summary — the same refusals shiftwork_plan and shiftwork_clock_in give for that file. With `search`, it also returns the lines of jobs Retention archived under the checkpoint's .shiftwork/archive/ that hold every word of the query. Opens only the checkpoint (and that archive directory when `search` is given); writes nothing, not even the log shiftwork_clock_in appends to.
- **build_identity** — Which bantamkit build is answering you, readable mid-call. Returns `build_id` — a content fingerprint of the running source and the asset pack it loads — plus the paths it was computed over. Call it when one MCP server name may resolve to more than one install: two endpoints reporting the same `version` are the SAME build only if their `build_id` matches, because a version string moves on release bumps and not on builds. Any fact that cannot be derived comes back as {"unavailable": "<reason>"} and is named in the `unavailable` list — never omitted, never a placeholder that reads as a value. Takes no arguments and reads no state. Use bantamkit_status when the question is whether the server is healthy or which store it is bound to — that report includes this fingerprint; call this tool when you need only the fingerprint, as JSON, to compare two endpoints.
- **bantamkit_status** — Is bantamkit working, and which bantamkit? Returns a short report a person can read in the transcript: `bantamkit Active 🟢` or `bantamkit Degraded 🟠` on the first line, then the version and `build_id` of the code answering, the surfaces it serves, the memory store it is bound to with its index size against its budget, whether the event log is on, and — when degraded — every condition in full with what to do about it. Call it when the user asks whether the server is alive or working, when a bantamkit tool answered oddly and you need to know whether the server is the reason, or when a reply arrived carrying a `bantamkit degraded` footer and you want the rest of it. Every fact is observed at call time from the server's own state; nothing is a heartbeat or a cached verdict. Takes no arguments, writes nothing, and never echoes another call's arguments. build_identity answers only the `build_id` fingerprint, as JSON, when all you need is to compare two installs; shiftwork_status reports a job, not the server.
- **memory_compact** — Free room in persistent memory when `memory_save` was refused for budget. Archives the stalest facts of the writable project store until a fact as large as the biggest one kept will fit, and returns what moved — every archived name with its type and description. Nothing is deleted: archived facts leave the index but stay on disk under archive/, memory_recall with `include_archived` still finds them, and bringing one back is an operator action rather than a tool — `bantamkit-memory restore <name>` (Node) or `python -m bantamkit.memory restore <name>` (Python) on the store; no tool deletes a fact. Read-only grants and the profile layer are never touched. Call it after a budget refusal, then retry the save; calling it under budget archives nothing and says so. It is not the pass for two copies of one name across layers — that is memory_dream.
- **skill_audit** — Price the skill catalogue every session pays for, and name the collisions in it. A skill's `description:` frontmatter is loaded into the agent's context in EVERY session; its body is read only when the skill is invoked, so the descriptions are the standing bill. Call it before adding or retiring a skill, or when two skills keep firing on the same phrase. Walks `root` for `<marketplace>/<plugin>/<version>/skills/<name>/SKILL.md` and answers one JSON document: `roots`, `skills` (counted), `catalogue_bytes` (UTF-8 bytes of their `description:` values), `findings` and `omissions`. A degraded server adds the footer as its last key, `bantamkit_degraded`. Read `findings` first — each is `{kind, severity, skills, detail}`: `shared-trigger-phrase` (high; two skills quote the same literal phrase, so which fires is a coin flip), `catalogue-over-budget` (high; only when `budget` is given), `never-invoked` (low; only when `usage` is given), `frontmatter-malformed` and `name-mismatch` (medium). Then `omissions`: counted skills plus omissions account for every SKILL.md found, each `{subject, count, size, what}` with subject one of `plugin-not-enabled`, `duplicate-skill`, `stale-version`, `unreadable-file`, `unparsed-frontmatter`. The tool reads nothing but `root`; the three facts a directory cannot answer come from the caller — `enabled` (plugin ids the host has switched on, `<plugin>@<marketplace>`; the cache also holds disabled plugins, so without it the bill is inflated), `usage` (call counts per skill) and `versions` (the version directory the host serves per plugin; a plugin absent from it gets the directory name that sorts LAST in byte order, which is wrong for `10.0.0` against `9.0.0`). Refused with their own sentence, never as a partial answer: an unknown `check`, a negative `budget`, an EMPTY `root`, a `root` that is missing or is a file. Full rules in docs/skill-audit.md.
- **memory_dream** — Consolidate the memories the project layer and the machine-wide profile layer hold under the SAME NAME, and say exactly what changed. Byte-identical copies collapse to one; a pair that has diverged is UNIONED — every claim from both copies survives, never a winner-takes-all — and a relative date in a body is annotated with the day it resolves to against that fact's own mtime, never against today. The survivor stays in the writable project layer and the profile copy MOVES to that store's archive/: nothing is deleted, and bringing a consumed copy back is an operator action, not a tool — `bantamkit-memory restore <name>` or `python -m bantamkit.memory restore <name>` on the profile store. This is a correctness pass, not a token saving: the profile index is derived at read time and is not loaded from a prompt, so consolidating it frees approximately no bytes — what it buys is one copy of a ruling instead of two that can disagree. It defaults to a DRY RUN because the profile store is machine-wide: a memory archived out of it stops answering for every other project on this machine that has no store of its own. Read the plan first, then call it again with dry_run false to apply it. For room in the index call memory_compact instead; for the facts themselves, memory_recall.
- **token_ledger** — What a session actually cost, read off the host's own transcripts. Walks `root` for `*.jsonl` transcripts — the host writes `<cwd-slug>/<session>.jsonl` and `<cwd-slug>/<session>/subagents/*.jsonl` beneath `~/.claude/projects` — and answers ONE JSON document: `root`, `transcripts`, `lines`, `requests` (distinct API requests), `totals` (the four classes: `input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `output_tokens`), `sessions` (one row each), `omissions`, and `cost` only when `model` is given. A degraded server adds the footer as its last key, `bantamkit_degraded`. Call it to answer 'what did this work cost' from the API's own `usage` blocks; nothing is estimated. Point `root` at ONE project's slug directory: there is no time window or summary mode, so a whole-corpus call answers a row per session ever recorded and blows the host's tool-result cap. The four classes are priced separately: on a real prompt 98.1 % of the tokens were `cache_read`. Counting is per `requestId`, never per line: one response is written as several records with the same `usage`, so the dedupe spans the whole walk and every later copy is a `duplicate-request` omission. A subagent's transcript carries its PARENT's `sessionId`, so its cost lands in the parent row as `sidechain_requests`. `lines` equals `requests` plus the sum of every omission's `count`, always. An omission is `{subject, count, what}`; `what` names the first site as `<relpath>:<line>` plus `and N more`. `cost` needs a rate: the shipped price table has none, so `{"unavailable": "no rate recorded for model ..."}` is the NORMAL answer, not an error; a rate enters through `$BANTAMKIT_PRICES` or `prices`. Refused with their own sentence: an EMPTY `root`, a `root` that is missing or is a file, an EMPTY `model`, and a token total above 2**53-1. The record rule and the nine omission subjects: docs/ledger.md.
- **work_plan** — Work plan: turn a dependency graph you hand it into the batches that may run in parallel — pure computation over `nodes`: no file opened, no path taken, nothing mutated. Use it when the graph is in your hands (tasks with depends_on); use shiftwork_plan when the graph is a shift-work checkpoint on disk, which it reads for you under the same batching rules. Batch k holds every node whose dependencies all sit in batches below k; `sequence` is the batches flattened; `width` is the largest batch — the fan-out an orchestrator must afford. Inside a batch: priority descending, then the order the nodes were given; both halves are contract, so two implementations answer byte-identically. Empty `nodes` is an ANSWER (no batches, width 0), not a refusal. Four refusals, in this order, one sentence each: `nodes[<i>].<field> must be ...` (an id that is not a string, depends_on not a list of strings, priority not an integer — named by position, never coerced), `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. `depends_on` encodes logical order, not file contention; give a node `touches` (repo-relative paths it will WRITE) and no batch ever holds two nodes sharing a path: inside a level, in the same order, a node whose path an admitted node already claims is held for the next batch, and the answer then carries `held: [{id, batch, by, path}]` (absent when no node declares `touches`). A path that is empty, absolute, holds a backslash or a `.`/`..`/empty segment is the fifth refusal, checked with the shape: `node <id> touches <json>, which is not a plain relative path`.
- **shiftwork_plan** — Shift-work plan: which units of a checkpoint may run NOW, and in what waves — the batches its `depends_on` graph permits, computed read-only from the file. Call it before dispatching a wave: `ready` (the first batch) is what shiftwork_clock_in will brief, and `width` is how many agents the widest wave needs. A `ready` member beyond the cursor is clockable only once clock-in has briefed it; without that brief, clock-out refuses it. Use shiftwork_status instead when you only want progress counts, and work_plan when the graph is yours to hand over as `nodes` rather than a checkpoint on disk — this tool reads the checkpoint's own plan.units. Units already `done` or `dropped` count as satisfied and are left out of every batch; `todo`, `in_progress` and `blocked` stay in. Order inside a batch is plan.units order, because the checkpoint schema has no priority field. It never moves plan.cursor — the cursor is echoed unchanged so the pointer and the batch view can be read side by side — and never writes anything. Refusals are structured, never exceptions: work_plan's sentences for a duplicate id, an unknown dependency or a cycle, and shiftwork_status's for a checkpoint it cannot read; a plan whose units are all terminal answers an empty `ready`, which is an answer, not a refusal. `depends_on` says nothing about two units writing the same file; a unit's optional `touches` (repo-relative paths it will WRITE) does: no batch holds two units sharing a path, a unit held out of one is reported in `held: [{id, batch, by, path}]` (absent when no unit declares `touches`), and a bad path is refused with work_plan's sentence. A unit whose `reviews` names a unit that is undeclared, not in its depends_on, or in its own role is refused: `unit <id> reviews <r> ...`. With job.budget declared it adds `budget` (spent, remaining, per_unit, affordable).
- **memory_queries** — Write, or list the facts that still lack, the likely-prompt queries recall matches a user's prompt against — for each fact, the prompts a user would type when that fact is what they need. Call it with {} at the end of a session that saved facts, or when bantamkit_status reports `queries: n of m facts` with n below m: it answers up to 10 facts with no queries yet (layer, name, description, a body excerpt), the writing instructions, and `remaining`. Then call it with {"facts": [{name, layer?, queries}]} to store what you wrote. Every item is checked before anything is written and the first bad one refuses the whole call — an unknown name or layer, more than 8 queries, one over 200 characters; `queries: []` removes a fact's entry. No fact file changes: queries live in a per-layer sidecar under ~/.bantamkit/queries/, keyed to the fact's content, so a fact edited later is listed as missing again. memory_save takes `queries` directly for a fact you are saving now; this tool is for facts already on disk.
- **shiftwork_draft** — Shift-work draft: turn tickets into a new job — a checkpoint that validates against the checkpoint schema, plus one brief file per ticket — so step 1 of the shift-work loop is not typed by hand. Call it once, when a job's tickets are settled and before the first shiftwork_clock_in; it never edits a job in progress (that is the planner's edit, or shiftwork_clock_out's `follow_ups`), and work_plan is the read-only answer when all you want is the batches. Every ticket needs a non-empty `id`, `title`, `role`, `verify`, `data_shape` (the types, fields, files or messages the unit produces or consumes — a ticket without one is not ready to execute) and `brief`; the first missing one is refused by name. The graph is work_plan's: its refusals come back verbatim, `batches` and `width` are its answer, and `cursor` is the first ready unit, the one shiftwork_clock_out would pick. With job.roles declared, a ticket role it does not name is refused. Each ticket becomes a `todo` unit with brief_path <briefs_dir>/<id>.md; any other ticket key (touches, reviews, irreversible, ...) is copied onto the unit, and the composed document must pass the checkpoint schema and shiftwork_plan's graph checks, or the schema's sentence is the refusal. `write` is false by default: the document and every brief's text come back and nothing is touched. With `write: true` the briefs and then the checkpoint are written by temp file + rename under <checkpoint>.lock; an existing checkpoint, or an existing brief with other bytes, is refused before anything is written, and a failure part-way names what landed in `written`. It never runs git, never runs `verify` and never reads the working tree: `repo` is taken as given. Refusals are {"result": "error", "reason"}, never exceptions.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "bantamkit-mcp": {
    "command": "npx",
    "args": ["-y","bantamkit-mcp@latest"]
  }
}
```

## Documentation

## What bantamkit-mcp does

bantamkit-mcp adds durable local memory to coding agents. Agents can preserve preferences, project decisions, and solutions, then retrieve them in later sessions instead of rebuilding the same context. Memory is stored as Markdown beneath `.bantamkit/memory`, with separate project and profile locations available.

The server also covers tasks beyond recall. Its capabilities include JSON Schema validation, a token ledger derived from host transcripts, skill-catalogue auditing, and shift-work operations for planning and handing off multi-step jobs. A matching Python library is available for small-model agents, while the MCP implementations for Node and Python share the same on-disk store.

## How it works

Memory writes target the project store. Recall can read the project store, stores explicitly granted through `.bantamkit/config.yaml`, and the profile store at `~/.bantamkit/memory`. Saved entries are deduplicated and kept within a byte budget. Compaction and dreaming tools help maintain the memory collection over time.

The server communicates with hosts over MCP using stdio. The npm and PyPI routes both support an install-once setup that records an absolute executable path, allowing later launches to run without network access or dependence on the shell’s `PATH`. Claude Code can optionally use hooks to add the memory index at session start, surface relevant facts on prompts, and encourage a save after a long session with no saved memory.

## Setup and configuration

For a Node installation, run `npx -y bantamkit-mcp@latest --install <host>`, replacing `<host>` with `claude`, `claude-desktop`, `copilot`, or `cursor`. The installer writes the host entry, backs up files it changes, and avoids prompting. Python users can install the MCP extra into a persistent virtual environment with `pip install "bantamkit[mcp]"`, then run `bantamkit-mcp --install <host>`.

The host must be restarted after installation; Claude Code requires a new session. An alternative configuration launches `npx -y bantamkit-mcp` at every session, but that route checks the package registry on each launch and can hang when offline. Install-once mode is better suited to machines that must operate without network access.

## Tools and capabilities

- `memory_save` stores facts in the project memory.
- `memory_recall` retrieves relevant saved information.
- `memory_compact` and `memory_dream` maintain the memory store.
- JSON Schema validation checks structured data.
- A token ledger reads usage information from host transcripts.
- A skill-catalogue audit reviews available skills.
- Shift-work tools plan and hand off multi-step jobs.

## Limitations and notes

The Node and Python implementations share files, so both runtimes should point at the intended stores. Install-once entries record the executable path used during setup; changing Node versions requires reinstalling or forcing the installation. The server is designed for stdio hosts, and its offline behavior begins after the package has been installed locally.

_Full upstream README: https://allmcps.com/mcp/bantamkit-mcp/readme_

