bantamkit-mcp 0.34.0 → 0.34.1

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.
Files changed (2) hide show
  1. package/README.md +331 -616
  2. package/package.json +21 -2
package/README.md CHANGED
@@ -1,497 +1,239 @@
1
- # bantamkit-mcp
2
-
3
- The bantamkit MCP server as a pure-Node package: `npx bantamkit-mcp`, no Python, no
4
- `uv`, no `pipx`, no interpreter bootstrap. It serves the same twelve tools, the same
5
- `bantamkit_status` prompt and the same two resource templates as `runtime-py`'s server,
6
- reads and writes the same memory store, and is checked against the Python server frame by
7
- frame — 4500+ conformance cases, with every intentional difference written down as a
8
- ruling. It installs **two** commands, not one — the server and `bantamkit-memory`, the
9
- operator CLI for memory-store lifecycle; see *The second bin* below.
10
-
11
- Every number on this page was measured on the machine that wrote it, with a command you
12
- can rerun. Where something was not measured, it says so.
13
-
14
- ## Install and run
15
-
16
- ```jsonc
17
- // .mcp.json — project scope
18
- {
19
- "mcpServers": {
20
- "bantamkit": {
21
- "command": "npx",
22
- "args": ["-y", "bantamkit-mcp"]
23
- }
24
- }
25
- }
26
- ```
1
+ # bantamkit-mcp — memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop
27
2
 
28
- That is the whole install. No `pip`, no venv, no `PYTHONPATH`. `runtime-ts/mcp.json.example`
29
- in the repository is the annotated version, with the four forms and the two measured
30
- questions that decide between them.
3
+ **bantamkit-mcp** is a pure-Node **MCP server** that gives coding agents a per-person **memory**
4
+ store, JSON Schema validation and shift-work tools. Install it from **npm** with one `npx`
5
+ command, connect it to **Claude Code**, **Claude Desktop**, **Cursor** or **GitHub Copilot in VS
6
+ Code**, and every later launch starts **offline**. No Python, `pip`, `uv`, `pipx` or venv.
31
7
 
32
- > **AMENDED 2026-09-15 (job51) — this is no longer the recommended form.** The block above
33
- > resolves the package against the registry at every launch. When the registry cannot be
34
- > reached, it hangs silently, even with a warm cache (measured below). From 0.34.0,
35
- > `npx -y bantamkit-mcp@latest --install <host>` writes an entry that starts with no network.
36
- > See *Install once, run offline*. `mcp.json.example` now has five forms, and the kept install
37
- > is the first.
8
+ It serves the same twelve tools, `bantamkit_status` prompt and two resource templates as the
9
+ Python server on **PyPI** (`pip install "bantamkit[mcp]"`), and reads and writes the same memory
10
+ store. The two are compared frame by frame: 8,130 conformance cases on 2026-09-15, with every
11
+ intentional difference written down as a ruling.
38
12
 
39
- **Pass `-y`.** `npx` historically prompted before installing a package it had not seen,
40
- and stdin here is the JSON-RPC channel: a prompt that consumed one frame looks like a
41
- server that lost a request. npm 11.6.2 was measured sending that prompt to stderr rather
42
- than reading stdin, but that is version-dependent and `-y` costs nothing.
13
+ ## Contents
43
14
 
44
- **Pin the version.** `npx -y bantamkit-mcp` resolves `latest` and caches it, so two
45
- machines can run different builds from one identical config line. See *Silent version
46
- float* below — it is the failure this package makes easiest to hit and hardest to see.
15
+ | Topic | What you'll find |
16
+ |---|---|
17
+ | [Install the MCP server](#install-the-mcp-server) | Two ways to launch it — pick one |
18
+ | [Install once, run offline (recommended)](#install-once-run-offline) | One command; later launches need no network |
19
+ | [Run with npx at every launch](#run-with-npx-at-every-launch) | The online form, and why it hangs offline |
20
+ | [Connect to a host](#connect-to-a-host) | The same five steps for every host |
21
+ | [Connect to Claude Code](#connect-to-claude-code) | `--install claude`, `claude mcp list`, `claude mcp remove` |
22
+ | [Connect to Claude Desktop](#connect-to-claude-desktop) | `claude_desktop_config.json` per OS |
23
+ | [Connect to Cursor](#connect-to-cursor) | `~/.cursor/mcp.json` |
24
+ | [Connect to VS Code (GitHub Copilot)](#connect-to-vs-code-github-copilot) | `mcp.json` with the `servers` key |
25
+ | [Connect other MCP clients](#connect-other-mcp-clients) | Any stdio host, and a GUI host's PATH |
26
+ | [Updating](#updating) | `--update`, the per-install table, then restart |
27
+ | [Troubleshooting](#troubleshooting) | Connection closed, timeouts, `ENOENT`, refusals, the wrong store |
28
+ | [Configuration](#configuration) | Every flag and environment variable, with its default |
29
+ | [Silent version float](#silent-version-float) | Why two machines run different builds, and `build_identity` |
30
+ | [The operator CLI: `bantamkit-memory`](#the-operator-cli-bantamkit-memory) | The second bin: status, lint, compact, archive, restore |
31
+ | [Memory stores are layered by default](#memory-stores-are-layered-by-default) | Why not to add `--store` by reflex |
32
+ | [The asset pack](#the-asset-pack) | What ships in `assets/` and how it is found |
33
+ | [Sharing a store with the Python server](#sharing-a-store-with-the-python-server) | Concurrent writes, and one hand-edit to avoid |
34
+ | [Development](#development) | Build, test, conformance |
35
+ | [Measurements and history](#measurements-and-history) | Where the measured numbers behind this page live |
36
+
37
+ ## Install the MCP server
38
+
39
+ A host launches the server every session, so what matters is whether each launch needs the
40
+ network. Pick **one** of the two ways below. To just try it: `npx -y bantamkit-mcp --assets-root`.
47
41
 
48
- ### Connect it to a host
42
+ ### Install once, run offline
49
43
 
50
- **One command, and it writes the entry for you:**
44
+ **Recommended.**
51
45
 
52
46
  ```bash
53
- npx -y bantamkit-mcp --install claude # Claude Code
54
- npx -y bantamkit-mcp --install claude-desktop # Claude Desktop
55
- npx -y bantamkit-mcp --install copilot # GitHub Copilot in VS Code
56
- npx -y bantamkit-mcp --install cursor # Cursor
47
+ npx -y bantamkit-mcp@latest --install claude # or claude-desktop, copilot, cursor
57
48
  ```
58
49
 
59
- > **ADDED 2026-09-15 (job51) — what these commands record changes in 0.34.0.** 0.33.0 and
60
- > earlier record `npx -y bantamkit-mcp`. From 0.34.0 they record
61
- > `<absolute node> <absolute dist/cli.js>`:
62
- >
63
- > - **Run from an npx cache**, as the lines above are, the `cli.js` belongs to a *kept
64
- > install* at `~/.bantamkit/mcp`. On Windows that is `%USERPROFILE%\.bantamkit\mcp`, which was
65
- > not measured. The kept install is made once with `npm install --prefix`, and only when it
66
- > does not already report this version.
67
- > - **Run from any other install** (`npm i -g`, `npm i --prefix`, a checkout), it is that
68
- > install's own `dist/cli.js`.
69
- >
70
- > If npm fails, no host file is read or written and no backup is taken. Add `@latest`
71
- > (`npx -y bantamkit-mcp@latest --install <host>`) so npx does not reuse an older cached
72
- > resolve. Two further points are explained in *Install once, run offline*. Moving from an
73
- > existing `npx` entry needs `--force`, and on Claude Code it needs `claude mcp remove`
74
- > first. The recorded node path names one node version.
75
-
76
- `--install claude` runs `claude mcp add` rather than editing `~/.claude.json` directly:
77
- that file is the host's, and it carries state that is not MCP configuration. What happens on
78
- a second run there is Claude Code's decision, not this command's, and `--force` does not
79
- reach it.
80
-
81
- **For the other three**, which are edited directly: it never prompts. A second run that
82
- finds its own entry says so and changes nothing; an entry that differs is refused, printed
83
- beside the one it would write, and replaced only with `--force`. Every write backs the file
84
- up first as `<name>.backup-<date>`, preserves the file's permissions, and a file that does
85
- not parse is refused rather than replaced.
86
-
87
- The rest of this section is what those commands write, for anyone who would rather do it by
88
- hand. Every host runs the same command; only the file and the key around it change.
89
-
90
- **AMENDED 2026-09-15 (job51) — the forms below are the online ones.** They are what 0.33.0
91
- and earlier write. From 0.34.0, `--install` writes the kept install instead, and that is also
92
- the recommended form to write by hand. Make the install once, then register node and its
93
- `cli.js`, both by absolute path:
50
+ From 0.34.0 this runs `npm install --prefix ~/.bantamkit/mcp` once (Windows:
51
+ `%USERPROFILE%\.bantamkit\mcp`, not measured) and records
52
+ `<the node that ran it> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js`, both absolute.
53
+ No later launch needs npx, the registry or your PATH. Measured from the published 0.34.0:
54
+ install exit 0 in 8.14 s; the recorded command then served 12 tools in 0.09 s with the network
55
+ cut.
56
+
57
+ - **`@latest` matters:** without it npx may reuse an older cached resolve.
58
+ - **It skips npm when the kept install already reports this version or a newer one.** It never
59
+ downgrades. If npm fails, no host file is read or written and no backup is taken.
60
+ - **Run from any other install** (`npm i -g`, `npm i --prefix`, a checkout), it records that
61
+ install's own `dist/cli.js` instead.
62
+ - **The recorded node is one version of node.** Switch away from it and re-run with `--force`.
63
+ - **Replacing an old `npx` entry needs `--force`** (Claude Code: `claude mcp remove` first).
64
+ - **Installing into another host while offline:** plain `npx -y` hangs before bantamkit starts.
65
+ Use `npx --offline -y bantamkit-mcp@0.34.0 --install <host>`, or the kept install's own CLI:
66
+ `<node> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js --install <host>`.
67
+
68
+ To make the kept install by hand, install once, then register node and `cli.js` by absolute
69
+ path (`node -p process.execPath` prints the node path). `-s user` makes it available in every
70
+ project; drop it for this project only:
94
71
 
95
72
  ```bash
96
- npm install --prefix ~/.bantamkit/mcp bantamkit-mcp@0.33.0
73
+ npm install --prefix ~/.bantamkit/mcp bantamkit-mcp@latest
97
74
  claude mcp add bantamkit -s user -- "$(node -p process.execPath)" ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js
98
75
  ```
99
76
 
100
- Both lines were run against a scratch `HOME`. npm exited 0, and Claude Code recorded
101
- `"command": "<…>/mise/installs/node/25.2.1/bin/node"` with the absolute `cli.js` as the one
102
- argument. For the three JSON hosts below, keep the file and the key, keep Copilot's
103
- `"type": "stdio"`, and use this `command`/`args` pair (`node -p process.execPath` prints the
104
- first path):
105
-
106
- ```json
107
- {"command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}
108
- ```
109
-
110
- **Claude Code** — one command, no file to edit. `-s user` makes it available in every
111
- project; drop it for this project only.
112
-
113
- ```bash
114
- claude mcp add bantamkit -s user -- npx -y bantamkit-mcp
115
- ```
116
-
117
- **Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json`
118
- on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows. Top-level key
119
- `mcpServers`; each entry takes `command`, `args` and an optional `env`.
77
+ ### Run with npx at every launch
120
78
 
121
79
  ```json
122
80
  {"mcpServers": {"bantamkit": {"command": "npx", "args": ["-y", "bantamkit-mcp"]}}}
123
81
  ```
124
82
 
125
- **GitHub Copilot in VS Code** — `.vscode/mcp.json` for one workspace, or the user
126
- profile via the **MCP: Open User Configuration** command. Note the top-level key is
127
- `servers`, not `mcpServers`.
128
-
129
- ```json
130
- {"servers": {"bantamkit": {"type": "stdio", "command": "npx", "args": ["-y", "bantamkit-mcp"]}}}
131
- ```
83
+ One config line (in `.mcp.json` for project scope, or a host's user config) and no install — but it asks the registry on **every** launch. Offline it fails
84
+ silently: with a warm cache, `npx -y bantamkit-mcp`, `npx -y bantamkit-mcp@latest` and
85
+ `npx -y bantamkit-mcp@0.33.0` each hung to the 45 s bound with 0 bytes on stdout, and the host
86
+ reports only its own handshake timeout.
132
87
 
133
- There is a CLI equivalent: `code --add-mcp '{"name":"bantamkit","command":"npx","args":["-y","bantamkit-mcp"]}'`.
88
+ - **Pass `-y`.** stdin is the JSON-RPC channel, and an install prompt that read one frame would
89
+ look like a server that lost a request.
90
+ - **Pin the version** (`bantamkit-mcp@0.34.0`) or add `@latest`: npx caches what `latest`
91
+ resolved to. See [Silent version float](#silent-version-float).
92
+ - **`npx --offline -y bantamkit-mcp@<version>`** serves a warm cache without the registry (0.40 s
93
+ measured), and fails at once with `npm error code ENOTCACHED` on a cache that never saw it.
134
94
 
135
- **Cursor** — `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` globally. Back
136
- to `mcpServers`.
95
+ `runtime-ts/mcp.json.example` in the repository is the annotated version, with five forms.
137
96
 
138
- ```json
139
- {"mcpServers": {"bantamkit": {"command": "npx", "args": ["-y", "bantamkit-mcp"]}}}
140
- ```
97
+ ## Connect to a host
141
98
 
142
- **Anything else that speaks MCP over stdio** runs `npx -y bantamkit-mcp` and talks
143
- JSON-RPC on its stdin and stdout. Nothing about this package is host-specific.
99
+ `--install <host>` (`claude`, `claude-desktop`, `copilot`, `cursor`) writes the entry. It never
100
+ prompts, so it behaves the same in a terminal, in CI and inside another agent. For the three
101
+ JSON hosts it backs the file up first as `<name>.backup-<date>`, keeps the file's permissions,
102
+ refuses a file that does not parse, and refuses an entry that differs unless you pass `--force`.
144
103
 
145
- The Claude Code and Claude Desktop forms were taken from this machine — `claude mcp add
146
- --help` and an existing config file. The VS Code and Cursor forms are from those
147
- projects' own documentation, not from a host installed here.
104
+ Each host below has the same steps: **1** command, **2** file, **3** entry, **4** confirm,
105
+ **5** undo or re-run. The entry's `command` and `args` depend on the install route:
148
106
 
149
- ### Measured: what a cold start costs
150
-
151
- `node tools/conformance/npx-cold-start.mjs` in the repo packs the tarball, installs it
152
- from disk into a cache that has never seen it, and drives a real MCP handshake. On
153
- node v25.2.1 / npm 11.6.2, macOS (darwin 25.5.0), Apple silicon:
154
-
155
- | | cold cache | warm cache |
107
+ | Route | `command` | `args` |
156
108
  |---|---|---|
157
- | wall to the first JSON-RPC frame | **3.90 s** and **5.09 s**, two runs | **1.14 s** and **1.11 s** |
158
- | npm/npx bytes on stderr | 211 (an `npm notice` about npm itself) | 0 |
159
-
160
- Both cold figures are reported rather than averaged, and the two in that column are two
161
- runs from **one sitting on 2026-08-25** — the column is labelled "two runs" and one
162
- execution cannot honestly fill it.
163
-
164
- **The spread is bigger than a range would suggest, and it is the network.** Five cold
165
- runs on that same day, same machine, same commit, measured **3.68 / 3.90 / 4.66 / 5.09 /
166
- 9.66 s**. An earlier revision of this table read "4.13 s and 7.36 s" and called a cold
167
- start "worth about 4-7 s here"; three of those five runs fall outside that range, so the
168
- range is dropped rather than re-fitted. What a reader should take from the cold column is
169
- an ORDER OF MAGNITUDE — seconds, dominated by the registry round trip, worse on a slower
170
- link — and not a number to compare a future run against. The warm figure is the one that
171
- is a property of this package, and it is stable across every run above.
172
-
173
- - **92 packages** installed (top-level under `node_modules`, scope-aware); **111**
174
- `package.json` **under `node_modules`**, which is the count of packages actually
175
- installed:
176
-
177
- ```
178
- find "$BED/cold/cache/_npx/<hash>/node_modules" -type f -name package.json | wc -l # 111
179
- ```
180
-
181
- Counting from the `_npx/<hash>` directory instead gives **112**, and the extra file is
182
- not a package: npx synthesises a `package.json` at that root holding the `file:` spec
183
- and an `_npx.packages` array. The method is written down here because the 112 was read
184
- once as a drift of one and it is not one — it is a different denominator. Reproduce
185
- either with `node tools/conformance/npx-cold-start.mjs --keep`, which prints the scratch
186
- path it leaves behind.
187
- - **15.7 MB** of files under `$npm_config_cache/_npx/<hash>` (26 MB of allocated blocks by
188
- `du`), **37.8 MB** for the whole cache including npm's content-addressable store (this
189
- read 37.7 until 2026-08-25; all four cold runs that day printed 37.8).
190
- - `bantamkit-mcp` itself is **0.9 MB** of that (1.2 MB allocated). The rest is
191
- `@modelcontextprotocol/sdk@1.30.0`'s dependency tree, which pulls in `express`, `cors`,
192
- `body-parser`, `ajv`, `eventsource`, `hono` and `express-rate-limit` — the SDK's HTTP
193
- transport, none of which this stdio server uses. That is the SDK's shape, not a choice
194
- this package makes; it declares exactly one runtime dependency.
195
- - The tarball is **~266 KB**, 137 files: 50 in `dist/`, 84 in `assets/`, plus `LICENSE`,
196
- `package.json` and this README. (The exact byte count moves whenever this file does;
197
- the gate prints it, and `test/packaging.test.mjs` pins the file list.)
198
-
199
- ### Measured: what happens when the registry is not reachable
200
-
201
- A cold `npx` start needs the network. Measured with the registry pointed at a closed port
202
- and at a blackholed address, driving the same handshake:
203
-
204
- | registry | seconds before `npx` gave up | bytes the client saw on stdout |
205
- |---|---|---|
206
- | connection refused (`http://127.0.0.1:1/`) | **140.3 s** | **0** |
207
- | packets dropped (`http://192.0.2.1:443/`) | **590.4 s** | **0** |
208
-
209
- **Read that as: the failure is silence.** Nothing appears on the JSON-RPC channel for the
210
- whole interval, and then the process exits 1. An MCP host does not see "no network"; it
211
- sees a server that accepted the launch and never answered `initialize`, and it reports its
212
- own handshake timeout. The npm error text goes to stderr, which most hosts do not surface.
213
-
214
- A warm cache does not have this problem — `npx` runs the cached install without contacting
215
- the registry — so this bites a new machine, a cleared cache, or a CI runner, which is
216
- exactly when nobody is watching.
217
-
218
- If offline operation matters, do not use `npx`. Run `npm i -g bantamkit-mcp` once and
219
- point the config at the installed binary, or keep using `tools/bantamkit-mcp`.
220
-
221
- > **AMENDED 2026-09-15 (job51) — "A warm cache does not have this problem" is false on npm
222
- > 11.6.2 for every spec a host config actually types.** Both paragraphs are kept rather than
223
- > rewritten, because the table above still holds. With a warm cache and the network cut,
224
- > `npx -y bantamkit-mcp@0.33.0`, `npx -y bantamkit-mcp` and `npx -y bantamkit-mcp@latest` all
225
- > hung silently, as the next section measures. "Point the config at the installed binary"
226
- > has a trap of its own under a GUI host: see the `.bin` row in that table. The next section
227
- > replaces both paragraphs.
228
-
229
- ### Install once, run offline
230
-
231
- `node tools/conformance/npx-cold-start.mjs --offline` printed every row below in a single run.
232
- It ran on 2026-09-15 at commit `2f58af4`, with node v25.2.1 and npm 11.6.2, on macOS 26.6.2 on
233
- Apple silicon, and took 299.94 s wall. It cuts the network by pointing `npm_config_proxy`
234
- and `npm_config_https_proxy` at `http://127.0.0.1:1`. It does not use `npm_config_registry`,
235
- which changes npm's cache key and would make a warm cache measure as cold. "GUI PATH" means
236
- `/usr/bin:/bin:/usr/sbin:/sbin`, the PATH a GUI-launched host inherits on this machine.
237
-
238
- | arm | outcome | wall | stdout |
239
- |---|---|---|---|
240
- | cold cache, registry refusing connections, `npx -y` | exit 1, 0 frames | 140.29 s | 0 bytes |
241
- | registry spec, warm cache, network cut: `npx -y bantamkit-mcp@0.33.0` | **silent hang**, killed at the 45 s bound | 45.01 s | 0 bytes |
242
- | the same, `npx -y bantamkit-mcp` | **silent hang** | 45.01 s | 0 bytes |
243
- | the same, `npx -y bantamkit-mcp@latest` | **silent hang** | 45.02 s | 0 bytes |
244
- | the same warm cache, network cut: `npx --offline -y bantamkit-mcp@0.33.0` | OK, 12 tools | 0.40 s | — |
245
- | local tarball spec (`--package=<file>.tgz`), warm cache, network cut: `npx -y` | OK | 1.35 s | 27987 bytes |
246
- | cold cache, `npx --offline -y` (tarball spec) | exit 1, `npm error code ENOTCACHED` | 2.69 s | — |
247
- | kept install: `npm install --prefix <dir> <tarball>`, once, online | OK | 4.82 s | — |
248
- | that install as `<abs node> <dir>/node_modules/bantamkit-mcp/dist/cli.js`, GUI PATH, network cut | OK, 12 tools | 0.09 s | — |
249
- | that install's `node_modules/.bin/bantamkit-mcp`, same PATH | exit 127, `env: node: No such file or directory` | 0.16 s | — |
250
-
251
- What the table says:
252
-
253
- - **The hang comes from a registry spec, not from npx in general.** npx resolves a registry
254
- spec against the registry at every start, so a warm cache does not save it. A local tarball
255
- on its own warm cache answered in 1.35 s with the same cut. Do not read the three hung rows
256
- as "every npx launch hangs offline".
257
- - **`npx --offline` is the middle option.** It serves a warm cache without the registry, in
258
- 0.40 s here. On a cache that has never seen the package it fails at once with
259
- `ENOTCACHED`. It suits a machine that has already run that exact spec online once, and it
260
- cannot start a new one.
261
- - **Only a kept install launched as node plus `cli.js` needs nothing.** No package is
262
- resolved and no PATH lookup happens. The `.bin` shim fails because its first line is
263
- `#!/usr/bin/env node`, and a GUI host's PATH has no `node`. An `npm i -g` bin runs the
264
- same file, so the global advice above hits the same trap unless `node` is on the host's
265
- PATH.
266
-
267
- **From 0.34.0, `--install` builds the kept install for you.** Run as
268
- `npx -y bantamkit-mcp@latest --install <host>`, it finds that it is running from an npx cache.
269
- It then installs `bantamkit-mcp@<its own version>` into `~/.bantamkit/mcp` with
270
- `npm install --prefix`, and records `<process.execPath> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js`
271
- with both paths absolute. It skips npm — and never downgrades — when the kept install already
272
- reports this version or a newer one; an unparseable kept version sorts as newer and is left
273
- alone too (J51-9a). Only an older, missing or blank kept version, or one whose `cli.js` is
274
- gone, runs npm. Measured in this unit: a kept install's manifest was hand-edited to
275
- `0.100.0` (newer than the `0.33.0` tarball below), then `--install`ed again with the network
276
- cut (`npm_config_proxy`/`npm_config_https_proxy` pointed at a closed port) — exit 0 in 0.355 s,
277
- npm never ran, and the kept manifest still read `0.100.0` afterward.
278
-
279
- **This installs the registry's package for that version, not this tree's code.** The `npm
280
- install --prefix` above asks the registry for `bantamkit-mcp@<version>`, so a checkout or an
281
- `npm pack` tarball only supplies the version *number*; the bytes that end up in
282
- `~/.bantamkit/mcp` are whatever the registry published under that number. Measured in this
283
- unit: a `0.33.0` tarball packed from this tree carries the string `J51-9a` twice in
284
- `dist/hostinstall.js` (`tar -xOzf bantamkit-mcp-0.33.0.tgz package/dist/hostinstall.js | grep
285
- -c J51-9a` → `2`), but after `--install cursor` ran from that same tarball, the kept
286
- `~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/hostinstall.js` carries it zero times — the
287
- kept install is the published `0.33.0`, which predates J51-9a, not the tree that was packed.
288
- Measured end to end from `npm pack` of this tree (version string still 0.33.0), in a scratch
289
- `HOME` with an empty npm cache — read the table below as *when* the install happens, not
290
- *whose code* it installs:
291
-
292
- | command | result |
293
- |---|---|
294
- | `npx -y -p <tgz> bantamkit-mcp --install cursor` | exit 0 in 11.60 s; wrote `"command": "/Users/…/mise/installs/node/25.2.1/bin/node"` and `"args": ["<HOME>/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]` |
295
- | the same with `--install copilot`, network cut | exit 0 in 0.35 s; npm did not run |
296
- | `--install cursor` over an existing `npx -y bantamkit-mcp` entry | exit 1: `cursor already has a bantamkit entry with different settings` … `re-run with --force to replace it` |
297
- | the same with `--force` | exit 0, `backup : …/mcp.json.backup-2026-09-15` |
298
-
299
- 0.34.0 was not yet on npm when this was written. Measured the same day,
300
- `npx -y bantamkit-mcp@0.33.0 --install cursor` still writes
301
- `"command": "npx", "args": ["-y", "bantamkit-mcp"]`. Until 0.34.0 is out, make the kept install
302
- by hand, as shown in *Connect it to a host*.
303
-
304
- **Two consequences to know.**
305
-
306
- 1. **The recorded node is one version of node.** Under mise, `process.execPath` is
307
- `~/.local/share/mise/installs/node/25.2.1/bin/node`. Uninstall or switch away from that
308
- version and the host can no longer spawn the server. Re-run
309
- `npx -y bantamkit-mcp@latest --install <host> --force` with the node you have now. nvm
310
- also keeps one directory per version; that was not measured here.
311
- 2. **Moving from an `npx` entry needs `--force`**, as the table shows. On Claude Code
312
- `--force` does not help: `claude mcp add` refuses a name it already has
313
- (`MCP server bantamkit already exists in user config`, exit 1, measured with Claude Code
314
- 2.1.270 against a scratch `HOME`). Run `claude mcp remove bantamkit -s user` first.
315
-
316
- **Updating the kept install.** From 0.34.0, `npx -y bantamkit-mcp@latest --update` run from
317
- an npx cache finds `~/.bantamkit/mcp`. It compares that install's version with the registry,
318
- and when they differ it runs `npm install --prefix ~/.bantamkit/mcp bantamkit-mcp@latest`.
319
- With no kept install, it refuses exactly as it did before. Measured with a kept 0.32.1 in a
320
- scratch `HOME`:
321
-
322
- ```console
323
- $ npx -y -p <tgz> bantamkit-mcp --update
324
- bantamkit-mcp 0.32.1 is installed; the package index has 0.33.0.
325
- updating from the package index: npm install --prefix <HOME>/.bantamkit/mcp bantamkit-mcp@latest
326
- the command printed:
327
- <npm's own output>
328
- updated bantamkit-mcp from 0.32.1 to 0.33.0.
329
- restart the server: a running bantamkit-mcp keeps serving the code it loaded at startup, so bantamkit_status will report 0.32.1 until the host reconnects.
330
- ```
331
-
332
- That run exited 0 in 1.58 s. A second run printed `up to date.` and exited 0. Then reconnect
333
- the server in the host. Nothing checks for updates at server startup, and startup never
334
- touches the network.
335
-
336
- ### Measured: `npx` is not on a login-less PATH
109
+ | npm, install once | `/absolute/path/to/node` | `["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]` |
110
+ | Python venv | `/absolute/path/to/env/bin/bantamkit-mcp` | `[]` |
111
+ | npx every launch | `npx` | `["-y", "bantamkit-mcp"]` |
112
+
113
+ A JSON file cannot expand `~`, so spell both paths out. To check a recorded command, run it in
114
+ a terminal: with nothing on stdin it prints `usage: bantamkit-mcp …` and exits 0.
115
+
116
+ ### Connect to Claude Code
117
+
118
+ 1. **Command:** `npx -y bantamkit-mcp@latest --install claude`
119
+ 2. **File:** none edited directly — it runs `claude mcp add bantamkit -s user -- <command> <args>`
120
+ (user scope, every project). `~/.claude.json` is the host's file, and it holds state that is
121
+ not MCP configuration.
122
+ 3. **Entry:** printed as `ran : claude mcp add bantamkit -s user -- <node> <cli.js>`.
123
+ 4. **Confirm:** `claude mcp list` prints `bantamkit: <node> <cli.js> - ✔ Connected` (Claude Code
124
+ 2.1.272; run it outside a project whose `.mcp.json` also names bantamkit, or it prints
125
+ `[Conflicting scopes]`). In a session, ask the agent to call `bantamkit_status`.
126
+ 5. **Undo / re-run:** `--force` does not reach Claude Code, and a second add fails with
127
+ `MCP server bantamkit already exists in user config`. Remove first:
128
+
129
+ ```bash
130
+ claude mcp remove bantamkit -s user
131
+ npx -y bantamkit-mcp@latest --install claude
132
+ ```
133
+
134
+ ### Connect to Claude Desktop
135
+
136
+ 1. **Command:** `npx -y bantamkit-mcp@latest --install claude-desktop`
137
+ 2. **File:** macOS `~/Library/Application Support/Claude/claude_desktop_config.json` ·
138
+ Windows `%APPDATA%\Claude\claude_desktop_config.json` ·
139
+ Linux `~/.config/Claude/claude_desktop_config.json`
140
+ 3. **Entry** (key `mcpServers`; each entry takes `command`, `args` and an optional `env`):
141
+
142
+ ```json
143
+ {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
144
+ ```
145
+
146
+ 4. **Confirm:** it prints `installed bantamkit into claude-desktop` with file, key and command.
147
+ Fully quit and reopen Claude Desktop, then ask it to call `bantamkit_status`.
148
+ 5. **Undo / re-run:** there is no uninstall flag — delete the `bantamkit` entry or restore
149
+ `claude_desktop_config.json.backup-<date>`. A matching re-run prints
150
+ `bantamkit is already installed in claude-desktop and matches`; a differing entry needs `--force`.
151
+
152
+ ### Connect to Cursor
153
+
154
+ 1. **Command:** `npx -y bantamkit-mcp@latest --install cursor`
155
+ 2. **File:** `~/.cursor/mcp.json` on every OS (`%USERPROFILE%\.cursor\mcp.json` on Windows);
156
+ `.cursor/mcp.json` for one project, by hand.
157
+ 3. **Entry** (key `mcpServers`):
158
+
159
+ ```json
160
+ {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
161
+ ```
162
+
163
+ 4. **Confirm:** it prints `installed bantamkit into cursor` and `key : mcpServers`. Restart
164
+ Cursor and ask the agent to call `bantamkit_status`.
165
+ 5. **Undo / re-run:** delete the entry or restore `mcp.json.backup-<date>`. To replace a
166
+ differing entry, such as an old `npx` one:
167
+
168
+ ```bash
169
+ npx -y bantamkit-mcp@latest --install cursor --force
170
+ ```
171
+
172
+ ### Connect to VS Code (GitHub Copilot)
173
+
174
+ 1. **Command:** `npx -y bantamkit-mcp@latest --install copilot`
175
+ 2. **File:** macOS `~/Library/Application Support/Code/User/mcp.json` ·
176
+ Windows `%APPDATA%\Code\User\mcp.json` · Linux `~/.config/Code/User/mcp.json`;
177
+ `.vscode/mcp.json` for one workspace, or **MCP: Open User Configuration**, by hand.
178
+ 3. **Entry** — the key is **`servers`**, not `mcpServers`, plus `"type": "stdio"`. This is the
179
+ detail that catches people out:
180
+
181
+ ```json
182
+ {"servers": {"bantamkit": {"type": "stdio", "command": "/absolute/path/to/node", "args": ["/Users/you/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js"]}}}
183
+ ```
184
+
185
+ VS Code's own CLI can add an entry too (from VS Code's documentation, not measured here):
186
+ `code --add-mcp '{"name":"bantamkit","command":"npx","args":["-y","bantamkit-mcp"]}'`.
187
+ 4. **Confirm:** it prints `installed bantamkit into copilot` and `key : servers`. Restart
188
+ VS Code and ask Copilot to call `bantamkit_status`.
189
+ 5. **Undo / re-run:** delete the entry or restore `mcp.json.backup-<date>`; `--force` replaces
190
+ a differing entry.
191
+
192
+ ### Connect other MCP clients
193
+
194
+ Anything that speaks MCP over stdio runs the same `command`/`args` and talks JSON-RPC on stdin
195
+ and stdout; nothing in this package is host-specific. Codex and a generic JSON config:
196
+ [docs/mcp.md → Client setup](https://github.com/Ink01101011/bantamkit/blob/main/docs/mcp.md#client-setup).
197
+
198
+ **A GUI-launched host does not read your shell rc.** If `node` and `npx` come from mise, nvm or
199
+ similar, they are not on its PATH (`/usr/bin:/bin:/usr/sbin:/sbin` on macOS). An `npx` entry then
200
+ fails with `ENOENT`, and a `.bin/bantamkit-mcp` shim exits 127 with
201
+ `env: node: No such file or directory`, because its first line is `#!/usr/bin/env node`. Name
202
+ node by absolute path, as `--install` does.
337
203
 
338
- ```sh
339
- $ env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin sh -c 'command -v npx'
340
- $ echo $?
341
- 1
342
- ```
343
-
344
- On the machine this was written on, `node`, `npm` and `npx` exist only on the
345
- mise-injected PATH, and mise is activated from `~/.zshrc` — an **interactive** shell rc.
346
- `launchctl getenv PATH` is unset, so a GUI-launched application inherits launchd's default
347
- `/usr/bin:/bin:/usr/sbin:/sbin`, where none of the three is found.
348
-
349
- **What a GUI-launched MCP host actually sees is `ENOENT` on `npx`.** Not a bantamkit
350
- error, not a bad config — the host reports that it could not spawn the command, which is
351
- the least informative message in the whole chain. This is the RB-P96 failure class
352
- relocated: the sh launcher was guaranteed present because it was a file in the repository;
353
- `npx` is guaranteed present only if the host's *process* environment has it.
354
-
355
- Two ways out, both in `runtime-ts/mcp.json.example`:
356
-
357
- ```jsonc
358
- // absolute path to the npx you actually have
359
- { "command": "/Users/you/.local/share/mise/installs/node/latest/bin/npx",
360
- "args": ["-y", "bantamkit-mcp@0.25.0"] }
361
- ```
204
+ ## Updating
362
205
 
363
- ```jsonc
364
- // or install once and skip npx entirely: npm i -g bantamkit-mcp
365
- { "command": "/usr/local/bin/bantamkit-mcp", "args": [] }
206
+ ```bash
207
+ npx -y bantamkit-mcp@latest --update
366
208
  ```
367
209
 
368
- > **AMENDED 2026-09-15 (job51) — the second form has the same problem one level down.**
369
- > `bantamkit-mcp` runs `dist/cli.js`, whose first line is `#!/usr/bin/env node`. Under this
370
- > PATH the kept install's `.bin` shim exited 127 (`env: node: No such file or directory`). A
371
- > global link runs the same file, though it was not launched separately here. Name node by
372
- > absolute path instead, as *Install once, run offline* shows.
373
-
374
- ## What `npx` gives up versus `tools/bantamkit-mcp`
375
-
376
- The sh launcher is not obsolete. It guarantees things `npx` cannot, and this table is the
377
- prep probe's, unsoftened.
378
-
379
- | guarantee | `tools/bantamkit-mcp` | `npx bantamkit-mcp` |
380
- |---|---|---|
381
- | present in every checkout **and** every worktree | yes — it is a file in the tree | **gone.** Depends on the host's PATH, not on the project directory |
382
- | runs *this* checkout's code | yes | **gone for developers too.** Testing a worktree needs an absolute `node <worktree>/dist/cli.js`, or the host silently runs the published build |
383
- | code from the worktree, deps from the main checkout | yes (`PYTHONPATH` + `PYTHONSAFEPATH`) | **no Node analogue.** `NODE_PATH` is ignored by ESM and there is no `-P`. `npm link` and workspaces are a different failure surface, not the same one solved |
384
- | stdin is the JSON-RPC channel | yes | yes, with `-y` |
385
- | names the cause when dependencies are missing | yes | the analogous failure is *no network*, and it has no message at all — see the table above |
386
- | starts without a network | yes | **no**, on a cold cache. *Amended 2026-09-15:* nor on a warm one, for a registry spec (silent hang at a 45 s bound; *Install once, run offline*). From 0.34.0 `--install` records a kept install that does start offline |
387
- | `--which`, for diagnosing which endpoint answered | the flag exists | **not in the npx CLI** — but `tools/bantamkit-mcp-node --which` has it, see below |
388
- | one config line, no clone, no venv | no | **yes.** This is the whole reason the package exists |
389
-
390
- ### `--which` is a launcher flag, not a package flag
391
-
392
- This section previously read "`--which` is deleted, not ported" and said "there is no
393
- Node `--which`, and none is planned". Both are now false, and the second was made false
394
- inside this repository: `tools/bantamkit-mcp-node` ships `--which`, and
395
- `runtime-ts/test/launcher.test.mjs` runs it — including on a checkout that has never been
396
- built, which is the case the Python flag's `find_spec` was chosen for. It prints
397
- `checkout=`, `deps_root=`, `runtime=node`, `node=`, `entry=` (suffixed `(missing)` when
398
- `dist/` is absent) and `sdk=`.
399
-
400
- What is genuinely not ported is `--which` **on the published package**, and the reason is
401
- the line above it in the table: `npx bantamkit-mcp` does not run a checkout, so
402
- `checkout=` and `source=` have nothing to report. The question a reader has about an npx
403
- endpoint is not *where did this resolve* but *which build answered*, and that is
404
- `build_identity` — a tool on the wire rather than a flag on a launcher, and what the next
405
- section is about.
406
-
407
- The old wording came from a real defect, which is now closed. `tools/bantamkit-mcp:47`
408
- used to name `tools/mcpreach/mcpreach.py` as `--which`'s consumer and `docs/mcp.md` used
409
- to document a five-value exit-code interface for that program (`0` REACHABLE, `1`
410
- UNREACHABLE, `2` FOREIGN, `3` UNDECLARED, `4` NO_ENV). **That program has never existed** —
411
- `git log --all --diff-filter=A -- '*mcpreach*'` is empty across every ref in this
412
- repository, and `docs/eval.md` records the decision not to ship it, the half-built checker
413
- having "never been seen to fire". Both citations were rewritten on 2026-08-24 to name what
414
- actually runs, and `runtime-py/tests/test_doc_commands_gate.py` is now red if any fenced
415
- shell block in the repository names a `tools/` program that is not in the tree.
416
-
417
- ## Silent version float, and the instrument for it
418
-
419
- This is the one that will bite a team hardest.
420
-
421
- `npx -y bantamkit-mcp` resolves the dist-tag `latest` and caches the result. Two people
422
- with byte-identical `.mcp.json` files can be running different builds — one resolved last
423
- week, one resolved this morning — and nothing in the config, the logs or the tool output
424
- says so. The sh launcher could not do this: it ran the checkout you were standing in.
425
-
426
- Call `build_identity`. Three fields answer it:
210
+ `--update` — `check the package index and update this install if it differs, then exit` — asks
211
+ the npm registry for `latest` and compares it with the installed version:
427
212
 
428
- - **`runtime`** — `"node"` here, absent on the Python server. Its *presence* is the
429
- discriminator, and it is folded into `build_id` so the two lineages cannot collide even
430
- if their code digests agreed.
431
- - **`assets_digest`** — sha256 over every byte of the asset pack, computed identically in
432
- both runtimes and **verified equal**: `sha256:b03141bf…` over 83 files from Python and
433
- from Node. This is the cross-runtime instrument; two machines disagreeing here are
434
- serving different data.
435
- - **`code_digest`** / **`build_id`** — over `dist/**/*.js` on this side and `*.py` on the
436
- other, so they are *required* to differ across runtimes and required to match across two
437
- installs of the same version. `build_id` differing between two teammates on the same
438
- version string is the float, caught.
439
-
440
- The output carries a `cross_runtime` sentence saying in plain words which fields are
441
- comparable. `git_commit` is refused rather than guessed: an installed npm tarball carries
442
- no repository, and reading a checkout's HEAD would describe the tree rather than the bytes
443
- that were imported (RB-P84).
444
-
445
- **Pin the version in the config** if you want this to be a non-issue:
446
- `"args": ["-y", "bantamkit-mcp@0.30.0"]`.
447
-
448
- A kept install (what `--install` records from 0.34.0) does not float either. It serves the
449
- version in `~/.bantamkit/mcp` until someone runs `--update`.
213
+ - **Same version:** prints both numbers and `up to date.`
214
+ - **Behind, on a registry install or a kept install from `--install` (0.34.0+):** runs the
215
+ matching row below and prints npm's output.
216
+ - **Behind, on any other shape** (a checkout, a linked tree, a local file, an npx cache with no
217
+ kept install): refuses with exit 1 and names your row. It will not write a registry install
218
+ into a tree you manage with `git`.
450
219
 
451
- ## Updating
220
+ It is the only network access here — not at startup, not on `bantamkit_status`, not on any tool
221
+ — with a 10-second timeout, and being offline is a named refusal on stderr, never a traceback.
452
222
 
453
- **Updating.** `bantamkit-mcp --update` — quoted verbatim from `--help`, run in this job
454
- (`node runtime-ts/dist/cli.js --help`, the same line as `.venv/bin/bantamkit-mcp --help`):
455
- `--update check the package index and update this install if it differs, then exit`. It asks
456
- the npm registry for `latest` and compares it to the version installed. If they match, it
457
- prints both numbers and `up to date.` and stops. If the index is ahead **and this is a
458
- registry install, or, from 0.34.0, a kept install made by `--install`**, it runs the row below
459
- that applies to you and prints what npm said. If the index is ahead and this is any OTHER
460
- shape — a checkout, a linked tree, a local file, or an npx cache with no kept install — **it
461
- refuses, exits 1, and names the row you are on**: it will not write a registry install into a
462
- tree you manage with `git`, and a command that exits 0 having changed nothing is worse than
463
- one that says no. Either way it ends by telling you to restart the server — **the running
464
- server keeps serving the code it loaded at startup**, so every row ends with restarting it in
465
- the host. The flag reaches the network and nothing else here does — not at startup, not on
466
- `bantamkit_status`, not on any tool — with a 10-second timeout, and being offline is a named
467
- refusal on stderr, never a traceback. The table below is the reference for what to do by hand,
468
- and it is what `--update` prints back at you when it will not act.
469
-
470
- > **J51-9b (2026-09-15).** This section used to open "There is no `bantamkit-mcp --update`,
471
- > deliberately," amended below rather than rewritten (2026-09-11, when the flag first
472
- > shipped). A review (F4) found that opening sentence sitting directly above this job's own
473
- > `--update` table row and flagged it as actively wrong, not merely stale, so this time the
474
- > paragraph is rewritten rather than amended again; it says nothing the 2026-09-11 amendment
475
- > did not already say, plus the 0.34.0 kept-install route this job added to the table below.
476
-
477
- | how it was installed | how to update |
223
+ | How it was installed | How to update |
478
224
  |---|---|
479
- | `npx -y bantamkit-mcp` in the host config | nothing to update — but npx **caches the resolved version**, so add `@latest` (or a pinned `@0.30.0`) or it will keep serving what it resolved weeks ago. `rm -rf ~/.npm/_npx` forces a clean resolve. |
480
- | `npx -y bantamkit-mcp --install <host>` from 0.34.0: a kept install at `~/.bantamkit/mcp` (added 2026-09-15) | `npx -y bantamkit-mcp@latest --update` patches it in place, or `npm i --prefix ~/.bantamkit/mcp bantamkit-mcp@latest`. The recorded paths do not change. Before 0.34.0, `--update` from an npx cache refuses instead |
225
+ | `npx -y bantamkit-mcp` in the host config | Nothing to update, but npx **caches the resolved version**: add `@latest` (or pin `@0.34.0`). `rm -rf ~/.npm/_npx` forces a clean resolve |
226
+ | `npx -y bantamkit-mcp --install <host>` from 0.34.0: a kept install at `~/.bantamkit/mcp` | `npx -y bantamkit-mcp@latest --update` patches it in place, or `npm i --prefix ~/.bantamkit/mcp bantamkit-mcp@latest`. The recorded paths do not change. Before 0.34.0, `--update` from an npx cache refuses instead |
481
227
  | `npm i -g bantamkit-mcp` | `npm i -g bantamkit-mcp@latest` |
482
228
  | `npm i --prefix <dir> bantamkit-mcp` | `npm i --prefix <dir> bantamkit-mcp@latest` |
483
229
  | PyPI (`pip install "bantamkit[mcp]"`) | `pip install -U "bantamkit[mcp]"` · pipx: `pipx upgrade bantamkit` · uv: `uv tool upgrade bantamkit` |
484
- | a checkout, via `tools/bantamkit-mcp-node` | `git pull && npm ci --prefix runtime-ts && npm run build --prefix runtime-ts` — this one never touches a registry, and `runtime-ts/dist/` is build output, so a pull alone changes nothing |
230
+ | a checkout, via `tools/bantamkit-mcp-node` | `git pull && npm ci --prefix runtime-ts && npm run build --prefix runtime-ts` — no registry, and `runtime-ts/dist/` is build output, so a pull alone changes nothing |
485
231
 
486
- **Then restart the server in your host**, or you will keep talking to the old build. In
487
- Claude Code that is `/mcp` → reconnect; in Claude Desktop it is a full restart of the app.
488
- Measured 2026-09-07 (served-tools: dated — the surface was eleven then; 0.30.0's
489
- `memory_dream` and `repo_map` made it thirteen): a checkout whose `dist/` had just been
490
- rebuilt at 0.30.0 kept answering `version 0.29.1, serving 11 tools` until the host
491
- reconnected — the disk was current and the process was not.
232
+ **Then restart the server in your host** — a running server keeps serving the code it loaded at
233
+ startup. In Claude Code: `/mcp` → reconnect. In Claude Desktop: a full restart of the app.
492
234
 
493
- **Verify with `bantamkit_status`, not with the install log.** It reports the version and
494
- the `build_id` of *the code that is answering you*:
235
+ **Verify with `bantamkit_status`, not the install log.** It reports the version and the
236
+ `build_id` of *the code that is answering you*:
495
237
 
496
238
  ```
497
239
  bantamkit Active 🟢
@@ -499,171 +241,138 @@ version 0.30.0, build sha256:4441619d…
499
241
  serving 12 tools, 1 prompt, 2 resource templates
500
242
  ```
501
243
 
502
- A version string that moved and a `build_id` that did not means you are reading a config,
503
- not a process.
504
-
505
- ### The failure this table exists for
244
+ A version that moved and a `build_id` that did not means you are reading a config, not a process.
506
245
 
507
- Measured on a real machine, 2026-09-07: a Claude Desktop entry pointed at
508
- `~/.local/share/bantamkit-mcp/node_modules/.bin/bantamkit-mcp`, which was **0.25.0** — five
509
- releases stale — and its `package.json` declared
246
+ **Tested a build from a local `.tgz`? Install over it from the registry afterwards**
247
+ (`npm i --prefix <dir> bantamkit-mcp@latest`). Otherwise that machine stays pinned to a `file:`
248
+ dependency in a temp directory that will be deleted, and `npm update` cannot help.
510
249
 
511
- ```json
512
- "bantamkit-mcp": "file:/private/tmp/.../scratchpad/bantamkit-mcp-0.25.0.tgz"
513
- ```
514
-
515
- a **local tarball in a temp directory that no longer existed**. `npm update` in that
516
- directory cannot help: the dependency does not name a registry. The fix is to install over
517
- it from the registry (`npm i --prefix ~/.local/share/bantamkit-mcp bantamkit-mcp@latest`),
518
- which restores a normal semver dependency and leaves the host config's path valid.
519
-
520
- If you install from a local `.tgz` to test a build, **install over it from the registry
521
- afterwards**, or that machine is pinned to a file that will be deleted.
250
+ ## Troubleshooting
522
251
 
523
- ## The second bin: `bantamkit-memory`
252
+ | Symptom | Fix |
253
+ |---|---|
254
+ | Connection closed at startup (`CONNECTION_CLOSED`) | Builds before 0.32.1 crashed when a GUI host started them with cwd `/`. Update, then restart the server |
255
+ | 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](#install-once-run-offline) |
256
+ | `ENOENT` from the host, or `env: node: No such file or directory` | A GUI host does not have your shell's PATH. `--install` records node by absolute path; see [Connect other MCP clients](#connect-other-mcp-clients) |
257
+ | `npm error code ENOTCACHED` | `npx --offline` on a cache that has never seen that spec. Run it once online, or use the kept install |
258
+ | `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>` |
259
+ | `MCP server bantamkit already exists in user config` | `claude mcp remove bantamkit -s user`, then install again |
260
+ | The server stopped launching after a node change | `npx -y bantamkit-mcp@latest --install <host> --force` with the node you have now |
261
+ | Two machines with one config run different builds | [Silent version float](#silent-version-float): compare `build_id` |
262
+ | `bantamkit_status` still shows the old version after an update | The host has not reconnected; restart the server |
263
+ | Recall finds nothing, or the wrong store | Set `BANTAMKIT_MEMORY_DIR`; the reply names the store it searched |
264
+ | `malformed fact file …` | A hand-edited fact has a bare non-string field; see [Sharing a store](#sharing-a-store-with-the-python-server) |
265
+ | `AssetNotFound: no assets directory found; set BANTAMKIT_ASSETS` | See [The asset pack](#the-asset-pack) |
266
+
267
+ ## Configuration
268
+
269
+ Flags go in the entry's `args`; environment variables in its `env` block (or
270
+ `claude mcp add -e NAME=value`). Both runtimes print the same `--help`.
271
+
272
+ | Setting | What it does | Default | Example |
273
+ |---|---|---|---|
274
+ | `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"}` |
275
+ | `--store STORE` | One store, layering off; outranks `BANTAMKIT_MEMORY_DIR` | off (layered) | `"--store", "/abs/store"` |
276
+ | `--start START` | Where store discovery starts; not with `--store` | cwd | `"--start", "/abs/project"` |
277
+ | Layered memory | Recall reads the project store, stores granted in `.bantamkit/config.yaml`, and `~/.bantamkit/memory`; saves go to the project store | on | [Memory stores are layered by default](#memory-stores-are-layered-by-default) |
278
+ | `--k K` | Default recall budget | `3` | `"--k", "5"` |
279
+ | `--index-budget BYTES` | Memory index byte budget | `24000` | `"--index-budget", "32000"` |
280
+ | `BANTAMKIT_EVENT_LOG` | Logs tool outcomes as JSONL | off; `on` → `<store>/events/mcp.jsonl`; other values are a path | `"env": {"BANTAMKIT_EVENT_LOG": "on"}` |
281
+ | `BANTAMKIT_ASSETS` | Your own asset pack (skills, rubrics, schemas) | the pack inside the package | `"env": {"BANTAMKIT_ASSETS": "/abs/my-assets"}` |
282
+ | `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` |
283
+ | `BANTAMKIT_PRICES` | Price table for the token ledger | `pricing/default.json` in the asset pack | `"env": {"BANTAMKIT_PRICES": "/abs/prices.json"}` |
284
+
285
+ One-shot commands that print and exit: `--install {claude,claude-desktop,copilot,cursor}` (with
286
+ `--force`), `--update`, `--assets-root`, `--mcp-report`, `--statusline`, `-h`. Typing
287
+ `bantamkit-mcp` at a terminal with nothing after it prints the help and exits 0; a host launch
288
+ down a pipe still starts the server, and `bantamkit-mcp --store /tmp/x` at a terminal still
289
+ serves. `--which` belongs to the checkout launchers `tools/bantamkit-mcp` and
290
+ `tools/bantamkit-mcp-node`, not to this package: an npx install runs no checkout, so ask
291
+ `build_identity` which build answered instead.
292
+
293
+ ## Silent version float
294
+
295
+ `npx -y bantamkit-mcp` resolves the dist-tag `latest` and caches the result. Two people with
296
+ byte-identical `.mcp.json` files can be running different builds — one resolved last week, one
297
+ this morning — and nothing in the config, the logs or the tool output says so. This is the one
298
+ that will bite a team hardest.
299
+
300
+ **Fix:** pin the version in the config (`"args": ["-y", "bantamkit-mcp@0.34.0"]`), or use the
301
+ kept install from `--install`, which serves the version in `~/.bantamkit/mcp` until someone runs
302
+ `--update`.
303
+
304
+ **Detect it:** call the `build_identity` tool. Three fields answer it:
524
305
 
525
- `package.json` declares **two** bins, so an install puts two commands on the path. Packed
526
- and installed into an empty scratch directory, that is what arrives:
306
+ - **`runtime`** — `"node"` here, absent on the Python server. Its *presence* is the
307
+ discriminator, and it is folded into `build_id` so the two lineages cannot collide.
308
+ - **`assets_digest`** — sha256 over every byte of the asset pack, computed identically in both
309
+ runtimes and verified equal (`sha256:eb79f9e3…` over 91 files from Python and from Node,
310
+ `node tools/conformance/run.mjs --all`, 2026-09-15). Two
311
+ machines disagreeing here are serving different data.
312
+ - **`code_digest`** / **`build_id`** — over `dist/**/*.js` here and `*.py` on the Python side, so
313
+ they must differ across runtimes and must match across two installs of one version. `build_id`
314
+ differing between two teammates on the same version string is the float, caught.
315
+
316
+ The output carries a `cross_runtime` sentence naming which fields are comparable. `git_commit` is
317
+ refused rather than guessed: an npm tarball carries no repository, and a checkout's HEAD would
318
+ describe the tree, not the bytes that were imported (RB-P84).
319
+
320
+ ## The operator CLI: `bantamkit-memory`
321
+
322
+ The package installs **two** commands: `bantamkit-mcp`, the server, whose stdout is the JSON-RPC
323
+ wire, and `bantamkit-memory`, the operator CLI for memory-store lifecycle, which prints reports.
324
+ It is a second bin rather than a subcommand because report output on the server's stdout would
325
+ corrupt the transport, and `bantamkit-mcp`'s help is byte-compared with
326
+ `python -m bantamkit.mcpserver -h`.
527
327
 
528
- ```console
529
- $ npm install ./bantamkit-mcp-0.25.0.tgz
530
- added 95 packages in 5s
531
- $ ls -l node_modules/.bin/
532
- bantamkit-mcp -> ../bantamkit-mcp/dist/cli.js
533
- bantamkit-memory -> ../bantamkit-mcp/dist/memory/cli.js
328
+ ```bash
329
+ npx -y -p bantamkit-mcp bantamkit-memory status # without installing; -p is required
534
330
  ```
535
331
 
536
- `bantamkit-mcp` is the server the `.mcp.json` line at the top launches, and its stdout is
537
- the JSON-RPC wire — not a thing you run by hand. `bantamkit-memory` is the operator CLI
538
- for memory-store lifecycle, and it prints reports. That is the whole reason it is a
539
- second bin instead of a subcommand: lifecycle output on the server's stdout would corrupt
540
- the transport, and `bantamkit-mcp`'s help is a byte-compared artifact against
541
- `python -m bantamkit.mcpserver -h`, which a subparsers action would move.
542
-
543
- **Amendment, 2026-09-11 (J46-26/J46-27).** "Not a thing you run by hand" is now half
544
- true and the half that changed is worth knowing. Typing `bantamkit-mcp` at a prompt
545
- with nothing after it no longer opens a mute server and blocks — it prints the help,
546
- on stdout, exit 0, the same bytes `-h` prints. The discrimination is whether **stdin is
547
- a terminal** and nothing else, so every host launch is unchanged: `"args": []` down a
548
- pipe still starts the server and still answers `initialize`. The paragraph above stays
549
- true of what it was describing — stdout is the wire, and nothing but frames goes down
550
- it when a host is on the other end. What is new is that typing the command to see what
551
- it does is now a reasonable thing to do. An argument after the command is an operator
552
- asking for a configured server and still gets one: `bantamkit-mcp --store /tmp/x` at a
553
- terminal serves. Compared between the two runtimes by
554
- `tools/conformance/suites/cli.mjs` (`bare-at-a-tty`, `flagged-at-a-tty`,
555
- `bare-over-a-pipe`).
556
-
557
- Five subcommands, scoped to the writable **project** layer only — read-only grants and the
558
- profile store are out of its reach by the code path, not by convention:
559
-
560
- | subcommand | what it does |
332
+ The package name and the bin name differ, so plain `npx bantamkit-memory` would look for a
333
+ package called `bantamkit-memory`.
334
+
335
+ | Subcommand | What it does |
561
336
  |---|---|
562
337
  | `status` | index size, budget, headroom, archive count |
563
338
  | `lint` | exit 1 if the store is malformed or over budget |
564
339
  | `compact` | archive the stalest facts until the index fits |
565
340
  | `archived` | list what compaction has moved out |
341
+ | `archive NAME` | move one named fact out |
566
342
  | `restore NAME` | move an archived fact back |
567
343
 
568
- Each takes `--store PATH` or `--start DIR`; with neither, it resolves the project store
569
- the way `Memory.layered()` does. Exit codes are `0` success, `1` a failure the operator
570
- must act on (over budget, a malformed fact, a refused restore), `2` a usage error — so
571
- `lint` drops into a pre-commit hook or a CI job unchanged.
572
-
573
- Driven off `node_modules/.bin/` from the install above, against a scratch five-fact store:
574
-
575
- ```console
576
- $ bantamkit-memory status --store store
577
- store: store
578
- facts: 5
579
- index: 576 bytes
580
- budget: 24000
581
- headroom: 23424
582
- archived: 0
583
- $ bantamkit-memory lint --store store --budget 400
584
- lint: FAIL — index is 576 bytes, budget is 400
585
- try: bantamkit-memory compact --store store --budget 400
586
- $ echo $?
587
- 1
588
- $ bantamkit-memory compact --store store --budget 400
589
- compacted 3 fact(s)
590
- index: 576 -> 236 bytes (budget 400, target 267, reserve 133, headroom 164)
591
- archived -> store/archive
592
- assets-pack-has-eighty-four-files (project, 133 bytes)
593
- ci-runner-is-macos-only (project, 97 bytes)
594
- conformance-runner-entrypoint (reference, 110 bytes)
595
- restore one with: bantamkit-memory restore <name> --store store
596
- $ bantamkit-memory archived --store store
597
- archived facts: 3 (store/archive)
598
- assets-pack-has-eighty-four-files
599
- ci-runner-is-macos-only
600
- conformance-runner-entrypoint
601
- $ bantamkit-memory restore ci-runner-is-macos-only --store store
602
- restored 'ci-runner-is-macos-only' — index now 333/24000 bytes
603
- ```
604
-
605
- `compact` printing every name that left is the point: `archive/` is a directory nothing
606
- reads back on its own, so a compaction whose output is not shown is a silent deletion as
607
- far as the operator is concerned.
608
-
609
- Without installing, `npx -p bantamkit-mcp bantamkit-memory status` runs it out of the
610
- registry. `-p` is not optional — the package name and this bin's name differ, and plain
611
- `npx bantamkit-memory` would go looking for a package called `bantamkit-memory`. Measured
612
- here against the local tarball rather than the registry, since this version is not
613
- published:
614
-
615
- ```console
616
- $ npx -y -p ./bantamkit-mcp-0.25.0.tgz bantamkit-memory status --store npxstore
617
- store: npxstore
618
- facts: 0
619
- index: 0 bytes
620
- budget: 24000
621
- headroom: 24000
622
- archived: 0
623
- ```
624
-
625
- **The Python install does not provide this command**, which is why the two spellings
626
- exist. Measured against the distribution in this repository's venv:
627
-
628
- ```console
629
- $ .venv/bin/python -c "from importlib.metadata import distribution; d=distribution('bantamkit'); print(sorted(e.name for e in d.entry_points if e.group=='console_scripts'))"
630
- ['bantamkit-mcp']
631
- $ env PATH="$PWD/.venv/bin:/usr/bin:/bin" sh -c 'command -v bantamkit-memory'
632
- $ echo $?
633
- 1
634
- ```
344
+ Each takes `--store STORE` or `--start START`; with neither, it resolves the project store the way
345
+ `Memory.layered()` does. It reaches the writable **project** layer only — read-only grants and the
346
+ profile store are out of reach by the code path, not by convention. Exit codes: `0` success, `1`
347
+ a failure to act on (over budget, a malformed fact, a refused restore), `2` a usage error, so
348
+ `lint` drops into a pre-commit hook or CI job unchanged. `compact` prints every name it moved,
349
+ because `archive/` is a directory nothing reads back on its own.
635
350
 
636
- One console script, and it is the server. The Python operator therefore types
637
- `python -m bantamkit.memory`, and the two CLIs are identical bytes after substituting one
638
- for the other — except the wrap, because argparse's hanging indent is
639
- `len(prefix) + len(prog) + 1`. The reason there is no third spelling both installs could
640
- use lives in one place, the `prog` row of `docs/porting.md`'s divergence table, and this
641
- page does not restate it. `docs/memory.md`'s *The operator CLI* is the full reference;
642
- `tools/conformance/suites/memorycli.mjs` is the gate that compares the two.
351
+ The Python install has no such console script: there the same CLI is
352
+ `python -m bantamkit.memory`, identical bytes apart from the program name and its wrap. Full
353
+ reference: [docs/memory.md → The operator CLI](https://github.com/Ink01101011/bantamkit/blob/main/docs/memory.md#the-operator-cli).
643
354
 
644
- ## The default is layered — do not add `--store` by reflex
355
+ ## Memory stores are layered by default
645
356
 
646
- With **no arguments**, which is what the config above passes and what production runs, the
647
- server binds `Memory.layered`: the project store discovered from the working directory,
648
- plus the profile layer, plus any `extra_stores` from `.bantamkit/config.yaml`. Recall lines
649
- are prefixed with their layer — `[project] `, `[extra:<name>]`, `[profile]`.
357
+ With **no arguments** — what every config above passes, and what production runs — the server
358
+ binds `Memory.layered`: the project store discovered from the working directory, plus the profile
359
+ layer, plus any `extra_stores` from `.bantamkit/config.yaml`. Recall lines are prefixed with their
360
+ layer: `[project] `, `[extra:<name>]`, `[profile]`.
650
361
 
651
- `--store <path>` binds one store and drops the tag. It is a debugging flag. A recall string
652
- produced under `--store` is not a string the deployment ever emits, and the cold-start gate
653
- asserts the layered form specifically for that reason.
362
+ `--store <path>` binds one store and drops the tag. It is a debugging flag: a recall string
363
+ produced under `--store` is not one the deployment ever emits, and the cold-start gate asserts the
364
+ layered form for that reason.
654
365
 
655
366
  ## The asset pack
656
367
 
657
- The pack is language-agnostic data (tool manifests, schemas, skills, rubrics, contract
658
- wording, eval fixtures) that lives at the **repository root**, one level above this
659
- package, and is shared verbatim with `runtime-py`. npm cannot reach outside a package
660
- directory, so `scripts/sync-assets.mjs` vendors it into `runtime-ts/assets/` on `prepack`;
661
- that directory is generated and git-ignored.
368
+ The pack is language-agnostic data — tool manifests, schemas, skills, rubrics, contract wording,
369
+ eval fixtures — that lives at the **repository root** and is shared verbatim with `runtime-py`.
370
+ npm cannot reach outside a package directory, so `scripts/sync-assets.mjs` vendors it into
371
+ `runtime-ts/assets/` on `prepack`; that directory is generated and git-ignored.
662
372
 
663
- `build_identity` hashes **every byte of the whole tree**, not just the 20 files the tools
664
- read, so the pack ships whole — 83 files / 212,480 bytes — or `assets_digest` and
665
- `build_id` change. `test/packaging.test.mjs` asserts that against what `npm pack` would
666
- actually put in the tarball, never against the source tree.
373
+ `build_identity` hashes **every byte of the whole tree**, not just the 20 files the tools read, so
374
+ the pack ships whole — 91 files / 238,751 bytes on 2026-09-15 — or `assets_digest` and `build_id` change.
375
+ `test/packaging.test.mjs` asserts that against what `npm pack` would put in the tarball.
667
376
 
668
377
  `assetsRoot()` mirrors `runtime-py/src/bantamkit/assets.py` arm for arm:
669
378
 
@@ -674,23 +383,19 @@ actually put in the tarball, never against the source tree.
674
383
 
675
384
  ## Sharing a store with the Python server
676
385
 
677
- Both servers read and write the same memory store, and byte-compatibility is the product,
678
- not a nice-to-have. Two things to know:
386
+ Both servers read and write the same memory store; byte-compatibility is the product.
679
387
 
680
388
  - **Concurrent writes are safe for the memory store and lossy for the checkpoint, in both
681
- runtimes.** `index.md` is derived from the `facts/` directory, so the last writer
682
- re-enumerates everything and the index self-heals — measured over 8 trials of two
683
- processes × 15 saves, zero disagreements between `facts/` and `index.md`. A checkpoint is
684
- not derived that way: `shiftwork_clock_out` is read-modify-write and the write depends on
685
- the document it read, so two concurrent clock-outs on the *same cursor unit* both answer
686
- `ok` and one history entry is lost. Measured 10/10 trials on Node **and** 10/10 on the
687
- Python reference — reference behaviour reproduced, not something the port introduced. The
688
- cursor check serialises the normal case; only the same-unit collision loses.
389
+ runtimes.** `index.md` is derived from `facts/`, so the last writer re-enumerates everything
390
+ and the index self-heals: 8 trials of two processes × 15 saves, zero disagreements. A
391
+ checkpoint is not: `shiftwork_clock_out` is read-modify-write, so two concurrent clock-outs on
392
+ the *same cursor unit* both answer `ok` and one history entry is lost — 10/10 trials on Node
393
+ **and** 10/10 on the Python reference. The cursor check serialises the normal case; only the
394
+ same-unit collision loses.
689
395
  - **Do not hand-edit a fact file's `name`, `description` or `type` to a bare non-string.**
690
- `description: 2026` is a YAML integer, not the text `2026`. Python interpolates it and
691
- carries on; this port refuses the whole store with `malformed fact file …`. Quote it:
692
- `description: '2026'`. Every fact the tools *write* is quoted correctly, so this only
693
- reaches you through an editor. See `src/memory/factfile.ts:257`.
396
+ `description: 2026` is a YAML integer. Python interpolates it and carries on; this port refuses
397
+ the whole store with `malformed fact file …`. Quote it: `description: '2026'`. Every fact the
398
+ tools *write* is quoted correctly (`src/memory/factfile.ts:257`).
694
399
 
695
400
  ## Development
696
401
 
@@ -704,13 +409,23 @@ npm pack --dry-run # vendors the pack via prepack, lists the tarball
704
409
  From the repository root:
705
410
 
706
411
  ```sh
707
- node tools/conformance/run.mjs --all # Node vs Python, 4300+ cases
412
+ node tools/conformance/run.mjs --all # Node vs Python, 8,130 cases on 2026-09-15
708
413
  node tools/conformance/npx-cold-start.mjs # pack, cold npx, warm npx, PATH
709
414
  node tools/conformance/npx-cold-start.mjs --offline # + the no-network arms (299.94 s on 2026-09-15)
710
415
  ```
711
416
 
712
- The conformance harness shells out to a CPython to produce the reference side. That is a
417
+ The conformance harness shells out to a CPython for the reference side. That is a
713
418
  **development-time** dependency of the test tooling; nothing under `tools/` ships, and
714
- `files: ["dist", "assets"]` is the whole published surface.
715
-
716
- Runtime dependencies: exactly one, `@modelcontextprotocol/sdk`, pinned to `1.30.0`.
419
+ `files: ["dist", "assets"]` is the whole published surface. Runtime dependencies: exactly one,
420
+ `@modelcontextprotocol/sdk`, pinned to `1.30.0`.
421
+
422
+ ## Measurements and history
423
+
424
+ Every number on this page was measured on the machine that wrote it, with a command you can
425
+ rerun. The measurements, dated amendments and transcripts that used to fill this page are in
426
+ [docs/install.md → npm package: measured detail](https://github.com/Ink01101011/bantamkit/blob/main/docs/install.md#npm-package-measured-detail):
427
+ cold-start cost and package size, the registry-unreachable and offline tables, what `--install`
428
+ recorded before and after 0.34.0, `--update` transcripts, the login-less PATH probe, what `npx`
429
+ gives up versus the `tools/bantamkit-mcp` launcher, and the `bantamkit-memory` session
430
+ transcripts. Where the two runtimes deliberately differ:
431
+ [docs/porting.md](https://github.com/Ink01101011/bantamkit/blob/main/docs/porting.md#where-the-two-runtimes-deliberately-differ).
package/package.json CHANGED
@@ -1,7 +1,26 @@
1
1
  {
2
2
  "name": "bantamkit-mcp",
3
- "version": "0.34.0",
4
- "description": "bantamkit MCP server — pure Node, runnable with npx",
3
+ "version": "0.34.1",
4
+ "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.",
5
+ "keywords": [
6
+ "mcp",
7
+ "mcp-server",
8
+ "model-context-protocol",
9
+ "claude",
10
+ "claude-code",
11
+ "claude-desktop",
12
+ "cursor",
13
+ "copilot",
14
+ "vscode",
15
+ "agent",
16
+ "memory",
17
+ "json-schema",
18
+ "llm"
19
+ ],
20
+ "homepage": "https://github.com/Ink01101011/bantamkit#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/Ink01101011/bantamkit/issues"
23
+ },
5
24
  "type": "module",
6
25
  "bin": {
7
26
  "bantamkit-mcp": "dist/cli.js",