Skip to main content
Glama

bantamkit โ€” memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop

npm version PyPI version Python versions Node version License: MIT npm downloads PyPI downloads

Give your coding agent a memory that outlasts the session. bantamkit is an MCP server: the agent saves what is worth keeping โ€” a preference you stated, a decision, the fix that worked โ€” and recalls it next session instead of asking you again. It works with Claude Code, Claude Desktop, Cursor, GitHub Copilot in VS Code and any stdio MCP client. Install it once from npm or PyPI; after that it starts offline.

๐Ÿš€ Quick start

npx -y bantamkit-mcp@latest --install claude   # or claude-desktop, copilot, cursor

Then restart the host (in Claude Code, open a new session) and ask the agent to call bantamkit_status. Prefer Python? pip install "bantamkit[mcp]" into a venv you keep, then bantamkit-mcp --install claude. Every route and host is below.

Related MCP server: mnemo

โœจ What you get

  • Memory that persists. Facts are plain Markdown files under .bantamkit/memory, deduped and held to a byte budget. memory_save and memory_recall do the work; memory_compact and memory_dream keep the store tidy.

  • Layered by default. Recall reads the project store, any stores you grant in .bantamkit/config.yaml, and your profile store ~/.bantamkit/memory. Saves go to the project store.

  • One command per host. --install writes the entry for Claude Code, Claude Desktop, Cursor or VS Code Copilot, backs up any file it edits, and never prompts.

  • Offline after one install. Both install routes record the server by absolute path, so no launch needs the network or your shell's PATH.

  • Claude Code hooks, if you want them. --install-hooks puts the memory index in front of each new session, surfaces matching facts on each prompt, and nudges a save at the end of a long session that saved nothing (docs/hooks.md).

  • More than memory. JSON Schema validation, a token ledger read off the host's transcripts, a skill-catalogue audit, and shift-work tools that plan and hand off multi-step jobs โ€” the fifteen tools.

  • Two runtimes, one store. Pure Node on npm and Python on PyPI read and write the same files, and every surface is compared between both runtimes by a conformance suite (node tools/conformance/run.mjs --all).

  • A Python library for small models, too. pip install bantamkit lifts small-model agents (~1Bโ€“8B, any OpenAI-compatible endpoint): malformed output, unreviewed answers, forgotten context and runaway loops are absorbed by code, and a bundled eval suite measures the uplift.

๐Ÿ“‘ Contents

Topic

What you'll find

Install the MCP server

Three ways to set it up โ€” pick one

Install once, run offline (recommended)

One command; later launches need no network

Run with npx at every launch

The online form, and why it hangs offline

Install with Python (PyPI)

A venv you keep; a wheelhouse for no-network machines

Connect to a host

The same five steps for every host

Connect to Claude Code

--install claude, claude mcp list, claude mcp remove

Connect to Claude Desktop

claude_desktop_config.json per OS

Connect to Cursor

~/.cursor/mcp.json

Connect to VS Code (GitHub Copilot)

mcp.json with the servers key

Connect other MCP clients

Codex and any stdio host

Configuration

Every flag and environment variable, with its default

Update

--update, then restart the server

Troubleshooting

Timeouts, Connection closed, refusals, the wrong store

What it serves

15 tools, three prompts, two resource templates

Use it as a Python library

pip install bantamkit and a minimal agent

Recommended defaults

Which components to attach, measured on four models

Documentation

Every page under docs/

Repo layout

What lives where

๐Ÿ“ฅ Install the MCP server

Two implementations of one server โ€” Node on npm, Python on PyPI โ€” share one memory store on disk. To just try it:

npx -y bantamkit-mcp --assets-root              # Node, no Python required
pipx run --spec "bantamkit[mcp]" bantamkit-mcp --assets-root   # Python

A host launches the server every session, so what matters is whether each launch needs the network. Pick one of the three ways below.

npx -y bantamkit-mcp@latest --install claude   # or claude-desktop, copilot, cursor

This runs npm install --prefix ~/.bantamkit/mcp once and records <the node that ran it> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js, both absolute. No later launch needs npx, the registry or your PATH, and it never downgrades a newer kept install.

  • The recorded node is one version of node. Switch away from it and re-run with --force.

  • Replacing an old npx entry needs --force (Claude Code: claude mcp remove first).

  • Installing into another host while offline: plain npx -y hangs before bantamkit starts. Use npx --offline -y bantamkit-mcp@<version> --install <host>, or the kept install's own CLI: <node> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js --install <host>.

Measurements: docs/install.md โ†’ MCP server: measured install detail.

Run with npx at every launch

{"mcpServers": {"bantamkit": {"command": "npx", "args": ["-y", "bantamkit-mcp"]}}}

This asks the registry on every launch. Offline it fails silently: with a warm cache, npx -y bantamkit-mcp, npx -y bantamkit-mcp@latest and a pinned version each hung to the 45 s bound with 0 bytes on stdout, and the host reports only its own handshake timeout. npx also caches what latest resolved to, so add @latest or pin a version (why).

Install with Python (PyPI)

pip install "bantamkit[mcp]"            # into a venv you keep, not a pipx run
bantamkit-mcp --install claude          # or claude-desktop, copilot, cursor

--install records the venv's console script by absolute path with "args": [], so no launch needs the network. With no network at all, pip download a wheelhouse on a machine with the same OS, CPU architecture and Python version, then pip install --no-index --find-links: steps.

๐Ÿ”Œ Connect to a host

--install <host> (claude, claude-desktop, copilot, cursor) writes the entry. It backs the file up first, refuses an entry that differs unless you pass --force, and never prompts โ€” so it behaves the same in a terminal, in CI and inside another agent. With Python, run bantamkit-mcp --install <host> from your venv instead of the npx line.

Each host below has the same steps: 1 command, 2 file, 3 entry, 4 confirm, 5 undo or re-run. The entry's command and args depend on the install route:

Route

command

args

npm, install once

/absolute/path/to/node

["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]

Python venv

/absolute/path/to/env/bin/bantamkit-mcp

[]

npx every launch

npx

["-y", "bantamkit-mcp"]

For JSON hosts, a quick check of the recorded command: run it in a terminal โ€” with nothing on stdin it prints usage: bantamkit-mcp โ€ฆ and exits 0.

Connect to Claude Code

  1. Command: npx -y bantamkit-mcp@latest --install claude

  2. File: none edited directly โ€” it runs claude mcp add bantamkit -s user -- <command> <args> (user scope, every project), because ~/.claude.json also holds host state that is not MCP config.

  3. Entry: printed as ran : claude mcp add bantamkit -s user -- <node> <cli.js>.

  4. Confirm: claude mcp list prints bantamkit: <node> <cli.js> - โœ” Connected (Claude Code 2.1.272; run it outside a project whose .mcp.json also names bantamkit, or it prints [Conflicting scopes]). In a session, ask the agent to call bantamkit_status.

  5. Undo / re-run: --force does not reach Claude Code, and a second add fails with MCP server bantamkit already exists in user config. Remove first:

    claude mcp remove bantamkit -s user
    npx -y bantamkit-mcp@latest --install claude

Connect to Claude Desktop

  1. Command: npx -y bantamkit-mcp@latest --install claude-desktop

  2. File: macOS ~/Library/Application Support/Claude/claude_desktop_config.json ยท Windows %APPDATA%\Claude\claude_desktop_config.json ยท Linux ~/.config/Claude/claude_desktop_config.json

  3. Entry (key mcpServers):

    {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
  4. Confirm: it prints installed bantamkit into claude-desktop with file, key and command. Fully quit and reopen Claude Desktop, then ask it to call bantamkit_status.

  5. Undo / re-run: there is no uninstall flag โ€” delete the bantamkit entry or restore claude_desktop_config.json.backup-<date>. A matching re-run prints bantamkit is already installed in claude-desktop and matches; a differing entry needs --force.

Connect to Cursor

  1. Command: npx -y bantamkit-mcp@latest --install cursor

  2. File: ~/.cursor/mcp.json on every OS (%USERPROFILE%\.cursor\mcp.json on Windows); .cursor/mcp.json for one project, by hand.

  3. Entry (key mcpServers):

    {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
  4. Confirm: it prints installed bantamkit into cursor and key : mcpServers. Restart Cursor and ask the agent to call bantamkit_status.

  5. Undo / re-run: delete the entry or restore mcp.json.backup-<date>. To replace a differing entry, such as an old npx one:

    npx -y bantamkit-mcp@latest --install cursor --force

Connect to VS Code (GitHub Copilot)

  1. Command: npx -y bantamkit-mcp@latest --install copilot

  2. File: macOS ~/Library/Application Support/Code/User/mcp.json ยท Windows %APPDATA%\Code\User\mcp.json ยท Linux ~/.config/Code/User/mcp.json; .vscode/mcp.json for one workspace, or MCP: Open User Configuration, by hand.

  3. Entry โ€” the key is servers, not mcpServers, plus "type": "stdio". This is the detail that catches people out:

    {"servers": {"bantamkit": {"type": "stdio", "command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
  4. Confirm: it prints installed bantamkit into copilot and key : servers. Restart VS Code and ask Copilot to call bantamkit_status.

  5. Undo / re-run: delete the entry or restore mcp.json.backup-<date>; --force replaces a differing entry.

Connect other MCP clients

Any stdio MCP client runs the same command/args; nothing is host-specific. Codex and a generic JSON config: docs/mcp.md โ†’ Client setup. Hand-written forms for every host: npm README ยท Python README.

๐Ÿ”ง Configuration

Flags go in the entry's args; environment variables in its env block (or claude mcp add -e NAME=value). Both runtimes print the same --help.

Setting

What it does

Default

Example

BANTAMKIT_MEMORY_DIR

Pins the store to one absolute path; a missing or relative path refuses at startup

unset: nearest existing .bantamkit/memory at or above the start directory

"env": {"BANTAMKIT_MEMORY_DIR": "/abs/project/.bantamkit/memory"}

--store STORE

One store, layering off; outranks BANTAMKIT_MEMORY_DIR

off (layered)

"--store", "/abs/store"

--start START

Where store discovery starts; not with --store

cwd

"--start", "/abs/project"

Layered memory

Recall reads the project store, stores granted in .bantamkit/config.yaml, and ~/.bantamkit/memory; saves go to the project store

on

docs/memory.md โ†’ Layers

--k K

Default recall budget

3

"--k", "5"

--index-budget BYTES

Memory index byte budget

24000

"--index-budget", "32000"

BANTAMKIT_EVENT_LOG

Logs tool outcomes as JSONL

off; on โ†’ <store>/events/mcp.jsonl; other values are a path

"env": {"BANTAMKIT_EVENT_LOG": "on"}

BANTAMKIT_RETENTION

The daily Retention job: deletes session read ledgers older than 7 days, folds log lines older than 30 days, archives closed .shiftwork jobs 7 days after they close (verified before delete). It does not stop the daily skill audit, which has its own switch (next row)

on; off (or 0/false/no) turns it off

"env": {"BANTAMKIT_RETENTION": "off"} โ€” docs/storage.md

BANTAMKIT_SKILL_AUDIT

The daily skill audit (#205): the same once-a-day child audits your skills and writes ~/.bantamkit/skill-audit.json; bantamkit_status prints its last run. Its own switch: with BANTAMKIT_RETENTION=off and this on, the child still wakes once a day, audits, and deletes nothing. Off also silences the SessionStart skill-audit line, even over an older record; the skill_audit prompt still runs on request

on; off (or 0/false/no) turns it off

"env": {"BANTAMKIT_SKILL_AUDIT": "off"} โ€” docs/storage.md

BANTAMKIT_SKILL_AUTO

Skills from experience (#206): lets the daily run INSTALL a skill it mined (twice the bar, a gated draft, one a day, five live, 30 days of probation) and make the low-risk EDITS to installed skills (keyword widening, a Pitfalls line, a drafted description) and PROMOTE a skill used 3 times in 30 days to a native link, on its own. Off, the same work waits for you as proposals in the skill_propose prompt (a skill you approve there is promoted at once, #301); the judge still rolls back a change of bantamkit's that made things worse. bantamkit_status's skills: line ends with auto on or auto off (BANTAMKIT_SKILL_AUTO=on to opt in)

off in 0.37.0; on (or 1/true/yes, any case) opts in, and any other value leaves it off

"env": {"BANTAMKIT_SKILL_AUTO": "on"}

BANTAMKIT_SKILL_TRIGGERS

Skills from experience (#301): lets recall append ONE [skill] line when a trigger phrase mined from your own past prompts appears in the prompt (after every other line; the top hits are unchanged; never a promoted skill). Mining and stamping the phrases run either way

off until the #301 ship bar is measured; on (or 1/true/yes, any case) turns it on, and any other value leaves it off

"env": {"BANTAMKIT_SKILL_TRIGGERS": "on"} โ€” docs/skills.md

BANTAMKIT_REPLY_NATIVE

Reply-native (#292), opt-in: the hook tells the model to think in English and reply in your language (Thai, Japanese). on / 1 / true / yes turns it on; see docs/hooks.md

off

"env": {"BANTAMKIT_REPLY_NATIVE": "on"}

BANTAMKIT_REPLY_LANG

Reply-native (#292): a two-letter code (th, ja, en, ...) makes the reply language fixed, injected at every SessionStart except resume; unset follows each prompt. Only read when BANTAMKIT_REPLY_NATIVE is on. See docs/hooks.md

unset

"env": {"BANTAMKIT_REPLY_LANG": "th"}

BANTAMKIT_LLM_URL, BANTAMKIT_LLM_MODEL

Skills from experience (#206): an OpenAI-compatible endpoint (<url>/chat/completions) bantamkit itself may ask to draft a skill, after MCP sampling and before the host agent. Both must be set. No key is sent but Bearer none, which local endpoints (Ollama, LM Studio) accept. The daily run drafts through it only when BANTAMKIT_SKILL_AUTO is on

unset: no endpoint

"env": {"BANTAMKIT_LLM_URL": "http://localhost:11434/v1", "BANTAMKIT_LLM_MODEL": "qwen2.5-coder"} โ€” docs/skills.md

BANTAMKIT_SKILL_BUDGET

Skills from experience (#206): the catalogue budget in bytes (the summed description bytes of every skill the host loads) that promoting a skill to a native link may not exceed

8192; any value that is not 1โ€“15 digits falls back to it

"env": {"BANTAMKIT_SKILL_BUDGET": "12000"}

BANTAMKIT_INDEX_DIR

Where the derived recall indexes live (hot and cold); deleting them loses nothing

~/.bantamkit/index

"env": {"BANTAMKIT_INDEX_DIR": "/abs/scratch/index"}

BANTAMKIT_ASSETS

Your own asset pack (skills, rubrics, schemas)

the pack inside the package

"env": {"BANTAMKIT_ASSETS": "/abs/my-assets"}

BANTAMKIT_HOST_LOG_ROOT

Where --mcp-report finds the host's MCP logs

macOS ~/Library/Caches/claude-cli-nodejs; elsewhere unset

BANTAMKIT_HOST_LOG_ROOT=/abs/logs bantamkit-mcp --mcp-report

BANTAMKIT_PRICES

Price table for the token ledger

pricing/default.json in the asset pack

"env": {"BANTAMKIT_PRICES": "/abs/prices.json"}

--which

Where a checkout launcher resolved its halves โ€” only tools/bantamkit-mcp and tools/bantamkit-mcp-node, not the npm/PyPI package

โ€”

tools/bantamkit-mcp --which

Don't add --store by reflex: the layering is most of the value. One-shot commands that print and exit: --install {claude,claude-desktop,copilot,cursor} (with --force), --install-hooks / --remove-hooks (with --yes), --hook, --update, --assets-root, --mcp-report, --statusline. More: store binding ยท pinning ยท event log ยท prices.

๐Ÿ”„ Update

npx -y bantamkit-mcp@latest --update        # patches the kept install at ~/.bantamkit/mcp
npx -y bantamkit-mcp@latest --assets-root   # npx CACHES; without @latest you get an old resolve
npm i -g bantamkit-mcp@latest               # global npm install
pip install -U "bantamkit[mcp]"             # PyPI (pipx upgrade bantamkit ยท uv tool upgrade bantamkit)
git pull && npm ci --prefix runtime-ts && npm run build --prefix runtime-ts   # a checkout: dist/ is build output

--update (check the package index and update this install if it differs, then exit) patches a registry install or a kept install to exactly the version it compared, and reports success only after the install says so; any other shape it refuses with exit 1 and names the manual route. It is the only network access here, and only when you type it.

Then restart the server in the host (/mcp โ†’ reconnect in Claude Code; a full restart of Claude Desktop) โ€” a running server keeps the code it started with. bantamkit_status prints the version and the build_id of the code answering you; a new version with an old build_id means an old process. Per-install table: npm README โ†’ Updating.

๐Ÿฉบ Troubleshooting

Symptom

Fix

The host times out; the server never answers

The entry uses npx with a registry spec and the network is down. Use Install once, run offline

ENOENT from the host

A GUI host does not read your shell rc, so npx/node is not on its PATH. --install records absolute paths

Connection closed at startup

Older builds crashed when a GUI host started them in /. Update; see docs/mcp.md

already has a bantamkit entry with different settings โ€ฆ re-run with --force to replace it

Re-run with --force; the old file is kept as .backup-<date> (.backup-<date>.1 and on for a later write the same day)

MCP server bantamkit already exists in user config

claude mcp remove bantamkit -s user, then install again

The server stopped launching after a node change

npx -y bantamkit-mcp@latest --install <host> --force with the node you have now

Recall finds nothing, or the wrong store

Set BANTAMKIT_MEMORY_DIR; the reply names the store it searched (the three states)

๐Ÿงฐ What it serves

Both servers serve the same 16 tools, three prompts and two resource templates (bantamkit://skills/{name}, bantamkit://rubrics/{name}). The prompts are what a person invokes from the host's slash menu: bantamkit_status (the status report), skill_audit (#205: gathers the skills this host loads, audits them, and inserts the report plus a proposed settings.json change that is never applied for you; in Claude Code it is /mcp__bantamkit__skill_audit) and skill_propose (#206: lists the skills bantamkit proposes from your repeated, successful work and the ones it installed, and approves, rejects, undoes, promotes or demotes one; in Claude Code it is /mcp__bantamkit__skill_propose, see docs/skills.md):

Tool

What it does

memory_save

store one durable fact, deduped and budgeted

memory_recall

retrieve facts matching a query

memory_compact

archive the stalest facts to fit the index budget

memory_dream

consolidate facts the project and profile layers hold under the same name

memory_queries

list the facts that have no likely-prompt queries yet, and store the queries written for them; recall matches a prompt against them

validate_json

validate a document against a JSON Schema

skill_audit

audit a skill catalogue for findings

shiftwork_clock_in

open a unit of work and get its brief

shiftwork_clock_out

close a unit with status and accounting

shiftwork_status

read-only progress summary of a checkpoint

shiftwork_plan

read-only: which units of a checkpoint its depends_on graph permits to run at once. Never moves the cursor

work_plan

turn any {id, depends_on, priority} graph into the batches that may run in parallel, plus the widest fan-out

token_ledger

what a session cost, read off the host's transcripts

bantamkit_status

is bantamkit working, and which build: version, build_id, store health against its budget

build_identity

report the fingerprint of the source on disk, not the executing code โ€” useful when a machine carries two installs under one name

๐Ÿ Use it as a Python library

pip install bantamkit

Editable install and runtime differences: docs/install.md, docs/porting.md. Copy-paste start: examples/.

from bantamkit import Agent, CritiqueGate, Memory, OpenAICompatible, Tool, ToolDef

client = OpenAICompatible(base_url="http://localhost:11434/v1", model="qwen2.5:7b-instruct")

price_lookup = ToolDef(
    tool=Tool(
        name="price_lookup",
        description="Get the unit price of an item",
        parameters={
            "type": "object",
            "required": ["item"],
            "properties": {"item": {"type": "string"}},
        },
    ),
    handler=lambda item: f"{item} price: 25",
)

agent = Agent(client=client, tools=[price_lookup]).use(
    Memory(store="./.bantam-memory"),
    CritiqueGate("task-completion"),
)

result = agent.run("What does a widget cost? Remember it for next time.")
print(result.output, result.usage.total)

Measured on the bundled 22-task suite across four models, 528 runs each. Numbers and caveats: docs/usage.md โ†’ Recommended defaults.

  • Always attach Memory โ€” the biggest single mover on every model measured.

  • Skip the blind CritiqueGate on small instruct models โ€” โ‰ค6 passes at 2โ€“3.5ร— bare's tokens.

  • Don't credit a rubric edit without a bar โ€” the non-fragile anchor set is the floor.

  • Use structured() for schema'd output โ€” zero schema retries in 2,112 runs.

  • Attach FileAccessGraph when the agent reads files, on ~4B-class models.

  • full (memory + schema + grounded critique) is a 4b-reference result โ€” 66/66 there only.

  • Prefer GroundedCritiqueGate over CritiqueGate when the agent has tools โ€” same 4b scope.

๐Ÿ“š Documentation

Page

Covers

Install

Requirements, editable install, endpoints, BANTAMKIT_ASSETS, measured MCP install detail

Architecture

The 5-layer model and the no-mixing rule

Usage

Client, agent, tools, components, structured(), errors, recommended defaults

Memory

Store layout, the four ops, dedupe and budget, layers, the operator CLI (python -m bantamkit.memory / bantamkit-memory)

File-access graph

The read ledger and the file_graph query tool

Eval

Running the suite, the config matrix, current results

MCP

bantamkit-mcp for Claude Code, Codex and any MCP client

Event log

The JSONL outcome log, its vocabulary and the 1 MiB cap

MCP report

--mcp-report: the host's MCP log joined with the event log

Status line

bantamkit-mcp --statusline and the Claude Code statusLine adapter

Hooks

--install-hooks: what each Claude Code hook entry does and what it costs

Status

bantamkit_status, line by line, and the degraded footer

Token ledger

token_ledger: what a session cost, and the price table

Skill audit

skill_audit: what the skill catalogue costs every session

Skills from experience

skill_propose: skills bantamkit mines from repeated, successful work, the [skill] layer, promotion and self-improvement

Shift-work

Checkpoint contract and clock-in/clock-out driver (experimental)

Porting

The Node port, PyScalar, and where the two runtimes deliberately differ

Conformance

The two-runtime diff harness and what a ruling: pins

1.0.0 roadmap

One TypeScript source, npm and PyPI targets, and release criteria

Releasing to npm

What ships and the pre-publish checklist

๐Ÿ“ Repo layout

Path

What

runtime-py/

The Python runtime (bantamkit package) and its test suite

runtime-ts/

The pure-Node MCP server (bantamkit-mcp on npm)

assets/

Language-agnostic asset pack: skills, rubrics, tool schemas, eval tasks

examples/

Runnable starter scripts (quickstart, structured output, layered memory)

tools/

Repo tools that ship outside the wheel (e.g. the shift-work driver)

docs/

This runbook

Design notes live in docs/superpowers/specs/.

Available Tools

16 tools
bantamkit_statusbantamkit statusA
Read-only

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. Pass detail: true to list every usage and touch ledger on its own line instead of one summary line per family. 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.

Inputs: detail. Returns: result

build_identityBuild identityA
Read-only

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": ""} 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.

Inputs: no named parameters. Returns: object

memory_compactCompact the memory storeA
Destructive

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.

Inputs: reserve. Returns: result

memory_dreamDream: consolidate the memory storeA
Destructive

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.

Inputs: dry_run. Returns: result

memory_queriesLikely-prompt queries for saved factsA

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.

Inputs: facts. Returns: facts, reason, result, written, +2 more

memory_recallRecall memoriesA

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

Inputs: k, file, query, outcome, +1 more. Returns: result

memory_saveSave a memoryA
Destructive

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.

Inputs: body (required), name (required), type (required), description (required), +5 more. Returns: result

shiftwork_clock_inShift work: clock inA

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 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 .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.

Inputs: checkpoint (required), unit_id. Returns: role, unit, files, do_not, +8 more

shiftwork_clock_outShift work: clock outA
Destructive

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 .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.

Inputs: status (required), unit_id (required), checkpoint (required), handoff_patch (required), +6 more. Returns: log, unit, cursor, reason, +2 more

shiftwork_draftShift work: draftA

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

Inputs: job (required), repo (required), tickets (required), write, +4 more. Returns: width, briefs, cursor, reason, +4 more

shiftwork_planShift work: planA
Read-only

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).

Inputs: checkpoint (required). Returns: held, ready, width, budget, +7 more

shiftwork_statusShift work: statusA
Read-only

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": ""} 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.

Inputs: checkpoint (required), search. Returns: units, cursor, reason, result, +3 more

skill_auditAudit the skill catalogueA
Read-only

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.

Inputs: root (required), check, usage, budget, +2 more. Returns: result

token_ledgerToken ledgerA
Read-only

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.

Inputs: root (required), model, prices. Returns: result

validate_jsonValidate JSON against a schemaA
Read-only

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.

Inputs: output (required), schema (required). Returns: valid, feedback

work_planWork plan: batch a dependency graphA
Read-only

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.

Inputs: nodes (required). Returns: held, width, reason, result, +2 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelogโ€ฆ

    TDQS

    A4.4/5.0

    Scored across 16 tools

    Disambiguation3/5

    Most tools target distinct resources, but several pairs have genuinely close boundaries: shiftwork_plan vs work_plan (same batching rules, file vs. in-memory graph), bantamkit_status vs build_identity (status already includes build_id), and memory_compact vs memory_dream (both archive). The descriptions work hard to disambiguate each of those explicitly, which is why this lands at 'some overlap but descriptions help' rather than lower.

    Naming Consistency4/5

    All names are snake_case and namespaced by domain (memory_*, shiftwork_*), which makes membership obvious at a glance. The ordering is mixed โ€” verb_noun (memory_save, validate_json, build_identity) alongside prefix_noun (shiftwork_clock_in, token_ledger, skill_audit) โ€” and the one-word neighbours shiftwork_plan/work_plan are a minor readability hazard, but nothing is chaotic.

    Tool Count4/5

    Sixteen tools spread across five distinct concerns (build/health, memory, shift-work orchestration, JSON validation, cost/skill auditing) means each area is small and the total is not padded. It sits at the upper edge of comfortable and a few tools (build_identity, memory_queries) could arguably fold into their siblings, but every tool has a defensible role.

    Completeness4/5

    Shift-work covers the full loop (draft โ†’ plan โ†’ clock_in โ†’ clock_out โ†’ status) and memory covers save/recall/tidy/reconcile/queries. The notable gaps are deliberate but still gaps: no tool to delete or restore an archived fact, no shift-work edit/archive operation, and skill_audit/token_ledger are purely read-only with no remediation โ€” all of which the descriptions route to operator CLI commands instead.

    Maintenance

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers