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.
- package/README.md +162 -21
- package/index.mjs +508 -435
- 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
|
-
> **
|
|
4
|
-
> change
|
|
5
|
-
> underneath are
|
|
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,
|
|
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.**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
90
|
-
revisions,
|
|
91
|
-
|
|
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:
|
|
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
|
-
| `
|
|
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` ·
|
|
146
|
-
`engram` — the retrieval
|
|
147
|
-
`create_store`
|
|
148
|
-
for nine
|
|
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
|
|
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`
|