wontopos-mcp 1.0.18 → 1.0.19

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 (3) hide show
  1. package/README.md +162 -21
  2. package/index.mjs +508 -435
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # wontopos-mcp — one memory across every AI tool
2
2
 
3
- > **Beta.** The ten tools work today and are tested, but the surface may still
4
- > change as we finish it. The [API and SDKs](https://wontopos.com/en/why)
5
- > underneath are stable and versioned.
3
+ > **Stable.** The tool names, their arguments and the shape of their results are a
4
+ > contract now: a change that would break a working install waits for a major version.
5
+ > The [API and SDKs](https://wontopos.com/en/why) underneath are versioned separately.
6
6
 
7
7
  [Wontopos (WOS)](https://wontopos.com) long-term memory as an [MCP](https://modelcontextprotocol.io) server.
8
8
 
@@ -10,7 +10,7 @@ Memory belongs to your **account**, not to a tool. The same store recalls in
10
10
  Claude Code, Claude Desktop, Cursor — and in ChatGPT via Actions with the same
11
11
  API ([openapi.json](https://api.wontopos.com/openapi.json)) — so a conversation
12
12
  started in one tool continues in another. The same recall in every language - no language
13
- is privileged - with bounded context (~1,200 tokens) per recall.
13
+ is privileged - with bounded context (~1,000 tokens on `tablet-2`, the default engine) per recall.
14
14
 
15
15
  ## Claude Code
16
16
 
@@ -59,14 +59,18 @@ Create a key in the [console](https://wontopos.com). Optional env:
59
59
  | `WONTOPOS_VERIFY` | Default re-ask passes for `search`, 0-3. Default `0`. Each pass is another engine call, billed for what it delivers; the agent can raise it per call. |
60
60
  | `WONTOPOS_MAX_IMAGES` | Default photo results per `search`, 0-5. Default `0` — see below. |
61
61
  | `WONTOPOS_BASE_URL` | A different API host. Leave unset for the default. |
62
+ | `WONTOPOS_HTTP_PORT` | Serve over HTTP instead of stdio, for hosts that cannot spawn a process. Needs `WONTOPOS_HTTP_TOKEN`. |
63
+ | `WONTOPOS_HTTP_TOKEN` | Bearer token every HTTP request must carry. The server refuses to start HTTP mode without one. |
64
+ | `WONTOPOS_HTTP_HOST` | Interface to bind in HTTP mode. Default `127.0.0.1` - this machine only. |
65
+ | `WONTOPOS_HTTP_ALLOWED_HOSTS` | Comma-separated `Host` values to accept in HTTP mode. A tunnel forwards its own name, and an undeclared one is refused 403. |
62
66
  | `WONTOPOS_READ_ONLY` | `1` / `true` / `yes` / `on` / `y` / `t` = read tools only, see Security. A value that is none of those (and none of `0` / `false` / `no` / `off`) stops the server rather than being read as off. |
63
67
  | `WONTOPOS_ALLOW_STORE_OVERRIDE` | `1` (same spellings) = multi-store; default = pinned, see Security. |
64
68
 
65
- **Why photos default to none.** The caller here is a model reading JSON. An image
66
- memory arrives as a row of metadata and a caption, not a picture it can look at, so
67
- the service default of one photo per search spends context on something the agent can
68
- only paraphrase. Raise `WONTOPOS_MAX_IMAGES` (or pass `max_images` on a single call)
69
- when the user is actually asking about photos.
69
+ **Why photos default to none.** Not because the model cannot see them - `view_image`
70
+ hands over the actual picture - but because the rows ride on *every* search, and most
71
+ searches are not about photos. Raise `WONTOPOS_MAX_IMAGES` (or pass `max_images` on a
72
+ single call) when the user is asking about pictures, then call `view_image` on the row
73
+ worth looking at.
70
74
 
71
75
  The server asks the service what the configured model can serve, once at boot, and
72
76
  says so in its startup line. On an engine without the photo channel or re-ask, those
@@ -81,14 +85,19 @@ quietly ignored, and that refusal would otherwise land on every search.
81
85
  stores? Register the server once per store (each entry stays pinned).
82
86
  - **Bounded results.** A tool result is capped at 128 KB (measured in UTF-8
83
87
  bytes, so the bound holds for CJK too) before it reaches the model, so
84
- an oversized memory cannot blow up the conversation context.
88
+ an oversized memory cannot blow up the conversation context. `view_image` is the
89
+ exception and is bounded differently: base64 cannot be trimmed without destroying
90
+ the picture, so a photo over 5 MB is refused rather than cut.
85
91
  - **Use a dedicated key.** Keys carry their workspace, so a key made just for
86
92
  MCP scopes what connected tools can ever touch. Rotate it in the console
87
93
  anytime without touching your app's keys.
88
94
  - **Read-only mode.** `WONTOPOS_READ_ONLY=1` registers no write tools at all:
89
- the agent can recall, search, list memories, run engrams, read stats and read
90
- revisions, but cannot store, update, forget, or delete. Right for agents that should consult
91
- memory, not own it.
95
+ the agent can recall, search, list memories, run engrams, read stats, read
96
+ revisions, view an image and read `usage`, but cannot store, record turns, update,
97
+ forget, or delete. `usage` is a read, so read-only mode still lets the agent see
98
+ the account's balance and this key's spend. Right for agents that should consult memory, not own it. The connect-time
99
+ instructions drop the `store_turn` guidance there too, rather than pointing the
100
+ model at a tool that is not registered.
92
101
  - **Keep tool confirmation on.** MCP hosts ask before running tools by default;
93
102
  leave that on for `forget` in particular (deletes are shared by every tool on
94
103
  the store).
@@ -107,17 +116,55 @@ quietly ignored, and that refusal would otherwise land on every search.
107
116
 
108
117
  | Tool | What the agent does with it |
109
118
  |---|---|
110
- | `recall` | One-call context: recent turns + relevant long-term memories + surroundings. First call when past context matters. |
119
+ | `recall` | One-call context: the **last 20 turns** (oldest first, one entry per utterance, so ten exchanges) plus relevant long-term memories and surroundings. First call when past context matters. `limit` (5-20) widens the sweep; `context_limit` (0-20) sets how much surrounding conversation rides along, `0` for the memories alone. Both need a limit-aware engine — an older one refuses the whole call, and the tool says which argument to drop. |
111
120
  | `remember` | Store what the user said that is worth keeping. `speaker: "me"` = the agent's own spoken commitments. Fetched content (search results, files) is not a memory. |
121
+ | `store_turn` | Record one exchange as short-term history - what the user said and what the agent replied. This is what fills `recall`'s 20-turn window — one call appends two entries — and nothing else writes it, so the agent is told to call it after **each** exchange. A turn is billed like any write and is kept long-term as well, searchable afterwards. |
112
122
  | `search` | Search stored memories. `filters` narrows by TIME or topic (`event_from`/`event_to` = when the content happened, `categories`) — for "what did we decide in June?", which a query alone will not pin down. `verify` (0-3) spends extra passes to reach further back; `max_images` (0-5) asks for photos. Answers with `memories`, plus `images` and `verify_used` when those applied. |
113
123
  | `update` | Rewrite a memory the user's own words contradicted - keeps what is still true, adds what changed. |
114
124
  | `forget` | Delete one memory by id. |
115
125
  | `list_memories` | Browse everything stored (original text + metadata, paged) - "what do you know about me?", housekeeping. |
116
126
  | `engram` | Multi-hop retrieval, no LLM: `deep_recall` (thorough), `timeline` (when did X), `gather` (everything related), `equilibrium` (replies that have started to loop or flatten), `tone_stabilizer` (back to the assistant's usual register). The API is the authority - an unknown name answers with the list it accepts. |
117
- | `stats` | Memory counts for a store. |
127
+ | `view_image` | Fetch the actual picture behind an image memory, as an image the model can look at rather than a caption it can only paraphrase. One photo per call. |
128
+ | `usage` | What this key has spent and what is left — its lifetime cost, its workspace and per-store cost over the last `days` days, and the account's prepaid balance. Free to call. Lets an agent decide whether to keep going. |
129
+ | `stats` | Counts for a store. Not the number of things a person told you, so it is a cheap check rather than an answer to "how much do you remember about me?" - `list_memories` answers that. |
118
130
  | `revisions` | How much of the store has been changed since it was written: counts by default, up to 20 memories on request. |
119
131
  | `create_store` | Creates the pinned store after asking the user (any store with `ALLOW_STORE_OVERRIDE=1`). |
120
132
 
133
+ ## Running it over HTTP yourself
134
+
135
+ Some hosts reach a URL rather than spawning a command. The same server answers over
136
+ HTTP when you give it a port:
137
+
138
+ ```bash
139
+ # Make the token first — the server never prints it, and callers need it.
140
+ TOKEN=$(openssl rand -hex 32); echo "bearer token: $TOKEN"
141
+
142
+ WONTOPOS_API_KEY=wos-live-... \
143
+ WONTOPOS_USER_ID=my-project \
144
+ WONTOPOS_HTTP_PORT=8787 \
145
+ WONTOPOS_HTTP_TOKEN="$TOKEN" \
146
+ npx -y wontopos-mcp
147
+ ```
148
+
149
+ Same thirteen tools, same store, same 20-turn window. Callers send
150
+ `Authorization: Bearer <token>`.
151
+
152
+ It binds `127.0.0.1` unless `WONTOPOS_HTTP_HOST` says otherwise, and **will not start without a token**: the reason to run this mode
153
+ is usually to put it behind a tunnel, and an open listener there is a public door to
154
+ your memory. Put a tunnel in front of the port rather than binding a public interface,
155
+ and treat the token like the API key.
156
+
157
+ **Behind a tunnel, name it.** The server checks the `Host` header, and a tunnel forwards
158
+ its own public name rather than `127.0.0.1` — so declare it or every call is refused 403
159
+ before a tool runs:
160
+
161
+ ```bash
162
+ WONTOPOS_HTTP_ALLOWED_HOSTS=your-name.ngrok.io # or the trycloudflare / Funnel name
163
+ ```
164
+
165
+ Loopback and the bind address are always accepted. The refusal names the header it saw
166
+ and the variable to set, and the boot line lists what is currently accepted.
167
+
121
168
  ## Hosted endpoint (for hosts that can't run a local process)
122
169
 
123
170
  Some MCP hosts only speak remote MCP — they cannot spawn `npx` on your machine. Those
@@ -142,10 +189,12 @@ connect to the hosted endpoint instead. Same memory, same stores, same account:
142
189
  by the model — the tools take no store argument, so a prompt-injected model cannot move
143
190
  to a sibling store.
144
191
 
145
- It offers `recall` · `search` · `remember` · `list_memories` · `stats` · `revisions` ·
146
- `engram` — the retrieval and write core. The curation tools (`update`, `forget`,
147
- `create_store`) are local-only for now. That is all ten; the two lists used to account
148
- for nine, and `revisions` was in neither.
192
+ It offers six of the thirteen: `recall` · `search` · `remember` · `list_memories` ·
193
+ `stats` · `engram` — the retrieval core plus one write. `update`, `forget`,
194
+ `create_store`, `revisions`, `store_turn`, `view_image` and `usage` are local-only. This list
195
+ has been wrong twice: it once accounted for nine tools with `revisions` in neither
196
+ column, and then named `revisions` as hosted when the endpoint has never served it.
197
+ Six is what the endpoint answers `tools/list` with today.
149
198
 
150
199
  The endpoint is a protocol adapter, not a second way in: every tool call re-enters the
151
200
  ordinary `/api/v1/memory` routes carrying your key, so the same authentication, rate
@@ -166,12 +215,105 @@ we ship?" — same memory answers.
166
215
 
167
216
  Uses the [`wontopos`](https://www.npmjs.com/package/wontopos) SDK underneath, so
168
217
  automatic retries, redirect refusal, response caps, and key hygiene apply as-is.
218
+ Pinned to one exact version, not a range: `search` and `recall` take their count
219
+ bounds from it, so a range would let two installs of the same wontopos-mcp
220
+ advertise different limits.
169
221
 
170
222
  - Docs: <https://wontopos.com/en/why>
171
223
  - Spec: <https://api.wontopos.com/openapi.json> · <https://wontopos.com/llms.txt>
172
224
 
173
225
  ## Changelog
174
226
 
227
+ - **1.0.19** - out of beta, and the release that made `recall` keep the other half of
228
+ its promise. It opens by offering "recent turns", and nothing in this server could
229
+ ever write one: turns go through their own API route, every tool here wrote
230
+ memories, so on an install where MCP is the only client `short_term.turns` was
231
+ permanently empty. `store_turn` writes them, and the connect-time instructions ask for
232
+ one after each exchange — a window only holds what is fed to it every turn — which is
233
+ what makes a conversation started in one tool continue in another rather than merely
234
+ being briefed by it. That is a write per exchange, billed as one, and kept long-term
235
+ and searchable like any other; an agent that should only consult memory belongs in
236
+ `WONTOPOS_READ_ONLY=1`, where `store_turn` is not registered at all. `recall` also takes `limit` and
237
+ `context_limit` at last; it had exactly one argument, so every answer was ten
238
+ memories with ten rows of context and no way to ask for a wider sweep or to decline
239
+ the context block, which is the larger half of the reply.
240
+
241
+ `recall` now states a short-term contract: **the last 20 turns**, one per utterance, oldest
242
+ first, alongside the long-term matches. The window is applied on the way out, and
243
+ `count` reports what was delivered. Turns were always in the reply, but when one had to be
244
+ trimmed to fit the byte cap they were halved **from the front** — keeping the oldest
245
+ and dropping what had just been said, which is backwards for the one list whose whole
246
+ value is recency. The recent end is kept now. `search` carries no turns and never did:
247
+ recent conversation belongs to `recall`.
248
+
249
+ **You can look at the photos now.** `view_image` returns the actual picture as an
250
+ MCP image block. The reason `max_images` defaulted to 0 was written down plainly —
251
+ "you get a row of metadata and a caption, not a picture you can look at" — and that
252
+ was true of this tool, never of the protocol: MCP has carried an image content block
253
+ all along and the SDK has had `getImage` since Tablet 2. The default stays 0 for the
254
+ reason that is still true (those rows ride on every search, and most searches are
255
+ not about photos), and the picture is now one call away.
256
+
257
+ **Cancelling a tool call stops paying for it.** Hosts cancel — a user presses stop,
258
+ a turn is abandoned — and the host stops waiting the moment it sends
259
+ `notifications/cancelled`. Nothing here was listening, so the API call carried on: a
260
+ `verify=3` search ran three engine passes for an answer nobody would read, billed in
261
+ full, and a retry backoff outlived the conversation that asked for it. Every tool
262
+ call now runs against a client bound to that request's signal.
263
+
264
+ **Every tool declares an `outputSchema` and answers with `structuredContent`**, so a
265
+ host gets the reply as data instead of re-parsing it out of a string. The schemas
266
+ name fields and explain them but assert no types: the SDK validates structured
267
+ content and a mismatch is a *protocol* error, so a schema strict enough to reject an
268
+ added service field would break a live call over a compatible change.
269
+
270
+ Four bugs, one of which was writing wrong data. **A bare number was accepted as an
271
+ `event_date`** — `Date.parse` does not refuse one, it answers: `"5"` is May 2001,
272
+ `"0"` is January 2000, `"202603"` is the year 202603. A model emitting a day number
273
+ or a unix timestamp got a fabricated date written onto the memory, and `event_date`
274
+ is the axis `event_from`/`event_to` search on, so the memory was filed under a date
275
+ nobody would look in and nothing about it read as wrong. **List arguments had no
276
+ length bound**: the per-entry cap measured each string and nothing measured the
277
+ list, so `filters.categories` and `revisions.skip_ids` took fifty thousand entries
278
+ and put half a megabyte on the wire — the hole 1.0.17 closed for scalars, still open
279
+ on the axis that multiplies. **`event_date` had no size cap at all**, the one
280
+ free-text argument the audit skipped. And **the truncation note overwrote a service
281
+ field of the same name**, so a reply carrying its own `_truncated` had it replaced
282
+ by ours.
283
+
284
+ New tool: **`usage`**. A model spending an account's money should be able to see the
285
+ bill. It answers with this key's lifetime cost, its workspace and per-store cost over
286
+ the window, and the prepaid balance that gates the next call — so "should I keep
287
+ searching or stop" has numbers behind it. Free, and it skips the balance gate: charge
288
+ for it and an account at zero cannot find out why, because the gate would refuse the
289
+ very call that explains the refusal. Scoped to the calling key; it never returns a
290
+ sibling key's spend.
291
+
292
+ From an audit against the SDK, four configuration mistakes that used to be reported
293
+ wrong. A key with a stray newline or a control byte in it — the mistake most likely
294
+ to be made by hand, pasting out of a terminal or a YAML block — **killed the process
295
+ with a stack trace out of `node_modules`**, which most MCP hosts show only as "the
296
+ server failed to start"; the SDK refuses those by throwing from its constructor and
297
+ nothing here caught it. Same for an unusable `WONTOPOS_MODEL`. Both now exit with one
298
+ sentence naming the variable, and the key is never echoed. An unparseable
299
+ `WONTOPOS_BASE_URL` was worse than either: the SDK never parses that value, so the
300
+ server announced itself **ready**, and then every call failed with "Failed to parse
301
+ URL" while the boot check said "continuing anyway" — the same situation as an
302
+ unusable store id, which has exited since 1.0.12, and now so does this.
303
+
304
+ The count range is no longer restated. `search` and `recall` build their bounds from
305
+ the SDK's exported `SEARCH_LIMIT_MIN`/`MAX` and `CONTEXT_LIMIT_MIN`/`MAX` instead of
306
+ spelling `5` and `20` into four places — this package has already shipped `search`
307
+ declaring 1..60 while `recall` said 5..20 and the service capped at 50, and a copy is
308
+ a copy whether or not it agrees today.
309
+
310
+ The oversized-reply path no longer produces unparseable text. A single memory too big to
311
+ drop used to be handled by slicing the serialized JSON — no closing bracket,
312
+ unparseable, and labelled as such, which is the one outcome the whole cap exists to
313
+ avoid. The long strings inside the object are cut instead: the same tail is lost and
314
+ what arrives is still a document. Underneath, the `wontopos` SDK floor moves to
315
+ **2.2.37**, the release that carries the `usage()` this server's new tool calls.
316
+
175
317
  - **1.0.17** - an install-wide default is no longer refused as though it had been
176
318
  asked for. `WONTOPOS_VERIFY` or `WONTOPOS_MAX_IMAGES` set against an engine without
177
319
  that capability made *every* search fail with "needs Tablet 2 or newer … search
@@ -271,8 +413,7 @@ automatic retries, redirect refusal, response caps, and key hygiene apply as-is.
271
413
  whole store and hoped. A filter chooses what is searched, not what is kept
272
414
  afterwards, so a narrow filter still fills your `limit` from inside it. The object is
273
415
  `.strict()`: a typo like `catagories` is refused instead of silently widening the search
274
- back to everything, and only customer-facing keys are accepted so the engine's internal
275
- filter vocabulary cannot be enumerated a guess at a time.
416
+ back to everything.
276
417
  - **1.0.8** - two bugs found by driving the server over stdio rather than reading
277
418
  it. A store id with stray whitespace **split the store in two**: the client was
278
419
  built from the raw env while the boot check, the ready line and `create_store`