wontopos-mcp 1.0.5 → 1.0.7
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/index.mjs +70 -12
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -27,15 +27,26 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
27
27
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
28
28
|
import { z } from "zod";
|
|
29
29
|
import { Client, WosError } from "wontopos";
|
|
30
|
+
import { createRequire } from "node:module";
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
// Read the version from package.json rather than restating it: a hand-kept copy
|
|
33
|
+
// silently drifts (1.0.6 shipped announcing itself as "1.0.5" in the MCP
|
|
34
|
+
// handshake, so hosts logged — and bug reports cited — the wrong build).
|
|
35
|
+
const VERSION = createRequire(import.meta.url)("./package.json").version;
|
|
32
36
|
const flag = (v) => ["1", "true", "yes"].includes((v ?? "").trim().toLowerCase());
|
|
33
37
|
const READ_ONLY = flag(process.env.WONTOPOS_READ_ONLY);
|
|
34
38
|
// Store pinning (default ON): every call goes to WONTOPOS_USER_ID and the tools
|
|
35
39
|
// do not even expose a user_id argument — a prompt-injected or confused model
|
|
36
40
|
// cannot reach a sibling store. Opt out per install with ALLOW_STORE_OVERRIDE.
|
|
37
41
|
const ALLOW_STORE_OVERRIDE = flag(process.env.WONTOPOS_ALLOW_STORE_OVERRIDE);
|
|
38
|
-
|
|
42
|
+
// Whether the store was CHOSEN or fallen back to. Worth tracking, because the
|
|
43
|
+
// fallback is silent in the worst way: if WONTOPOS_USER_ID never reaches this
|
|
44
|
+
// process (a typo in the host config, an --env that did not take), everything goes
|
|
45
|
+
// to `default` — and since every account is seeded with a `default` store, the boot
|
|
46
|
+
// check below SUCCEEDS, so nothing looks wrong while the configured store sits
|
|
47
|
+
// untouched. Say it out loud instead.
|
|
48
|
+
const STORE_FROM_ENV = (process.env.WONTOPOS_USER_ID || "").trim();
|
|
49
|
+
const STORE = STORE_FROM_ENV || "default";
|
|
39
50
|
// Cap what a tool result may inject into the model's context. The SDK's 64MB cap
|
|
40
51
|
// protects the process; this protects the conversation (a 2MB memory must not
|
|
41
52
|
// become 2MB of context). Measured in UTF-8 BYTES, not JS chars: `str.length`
|
|
@@ -54,6 +65,22 @@ function truncateToBytes(s, maxBytes) {
|
|
|
54
65
|
return s.slice(0, read);
|
|
55
66
|
}
|
|
56
67
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
68
|
+
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
69
|
+
/** Canonicalize event_date to the full RFC3339 the API requires.
|
|
70
|
+
* The API accepts only a complete date-TIME: a bare "2026-03-14" — the form a
|
|
71
|
+
* model naturally emits, and the form this tool's own example suggested — came
|
|
72
|
+
* back as an opaque 400 "Invalid request body or parameters." Normalize here
|
|
73
|
+
* instead of asking every agent to guess the shape.
|
|
74
|
+
* Returns null when the value is not a date at all, so the caller can refuse it. */
|
|
75
|
+
function normalizeEventDate(raw) {
|
|
76
|
+
const s = String(raw).trim();
|
|
77
|
+
if (Number.isNaN(Date.parse(s))) return null;
|
|
78
|
+
// Date-only is completed by string append, never through Date: parsing and
|
|
79
|
+
// re-serializing would re-interpret it in the host's zone and can shift the
|
|
80
|
+
// stated day by one, which is exactly the fact the user was pinning down.
|
|
81
|
+
if (DATE_ONLY_RE.test(s)) return `${s}T00:00:00Z`;
|
|
82
|
+
return new Date(s).toISOString();
|
|
83
|
+
}
|
|
57
84
|
|
|
58
85
|
// First env var with a NON-EMPTY value wins. `??` only skips null/undefined, so
|
|
59
86
|
// an empty WONTOPOS_API_KEY="" would shadow a valid WOS_API_KEY — the exact bug
|
|
@@ -167,7 +194,10 @@ server.registerTool(
|
|
|
167
194
|
"('Sunwoo prefers dark mode in the console'), not a chat log. For your own side, store only " +
|
|
168
195
|
"commitments you actually SAID to the user (speaker 'me') — never internal reasoning or thinking " +
|
|
169
196
|
"output. Do NOT store fetched material: web search results, crawled pages, file contents, and " +
|
|
170
|
-
"tool outputs are session context, not memories.
|
|
197
|
+
"tool outputs are session context, not memories. A memory close enough to one already stored is " +
|
|
198
|
+
"rejected as status 'duplicate' and is NOT saved — the match is by meaning, not by exact text, so " +
|
|
199
|
+
"when a new fact only narrows or contrasts an existing one ('allergic to shellfish' next to " +
|
|
200
|
+
"'allergic to peanuts'), write it as one sentence that states both, or supersede the old one with update.",
|
|
171
201
|
inputSchema: {
|
|
172
202
|
content: z.string().describe("The memory, as one self-contained sentence."),
|
|
173
203
|
speaker: z
|
|
@@ -177,7 +207,7 @@ server.registerTool(
|
|
|
177
207
|
event_date: z
|
|
178
208
|
.string()
|
|
179
209
|
.nullish()
|
|
180
|
-
.describe("
|
|
210
|
+
.describe("When the event actually happened, if different from now. A plain date ('2026-03-14') or a full RFC3339 timestamp; a bare date is read as UTC midnight."),
|
|
181
211
|
...storeArg,
|
|
182
212
|
},
|
|
183
213
|
},
|
|
@@ -187,11 +217,12 @@ server.registerTool(
|
|
|
187
217
|
throw new Error("content too long (max 8192 bytes) — a memory is one fact; backfill large text with the SDK's add_bulk.");
|
|
188
218
|
if (speaker && speaker !== "me")
|
|
189
219
|
throw new Error("speaker must be 'me' (something the agent itself said) or omitted (the user's fact).");
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
const eventDate = event_date ? normalizeEventDate(event_date) : null;
|
|
221
|
+
if (event_date && !eventDate)
|
|
222
|
+
throw new Error("event_date is not a parseable date — use a date (2026-07-01) or RFC3339 (2026-07-01T09:00:00Z).");
|
|
192
223
|
const metadata = {};
|
|
193
224
|
if (speaker) metadata.speaker = speaker;
|
|
194
|
-
if (
|
|
225
|
+
if (eventDate) metadata.event_date = eventDate;
|
|
195
226
|
return mem.add(content, uid(user_id), metadata);
|
|
196
227
|
})
|
|
197
228
|
);
|
|
@@ -306,10 +337,24 @@ server.registerTool(
|
|
|
306
337
|
"'deep_recall' searches then expands around the top match — thorough answer to one specific " +
|
|
307
338
|
"question; 'timeline' returns memories ordered by when events happened — 'when did X', " +
|
|
308
339
|
"histories; 'gather' expands around the top few matches — collect everything related to a " +
|
|
309
|
-
"topic
|
|
310
|
-
"
|
|
340
|
+
"topic; 'equilibrium' re-widens retrieval when a long session has narrowed it to whatever " +
|
|
341
|
+
"the current turn resembles — for replies that loop or flatten, not for a factual lookup; " +
|
|
342
|
+
"'tone_stabilizer' returns the assistant's OWN earlier words so it can come back to its " +
|
|
343
|
+
"usual register (needs turns stored with speaker \"me\"). " +
|
|
344
|
+
"Heavier than search, so reach for it when recall/search came back thin or the user asks " +
|
|
345
|
+
"for a full picture. Call the API's GET /api/v1/engram for the live catalogue.",
|
|
311
346
|
inputSchema: {
|
|
312
|
-
|
|
347
|
+
// A plain string, NOT an enum: the enum was pinned to the original three, so
|
|
348
|
+
// every engram the service added afterwards was uncallable from here until
|
|
349
|
+
// this package was republished. The API validates the name and answers 400
|
|
350
|
+
// with the list it accepts, so an unknown name fails clearly and a new engram
|
|
351
|
+
// works the day it ships.
|
|
352
|
+
name: z
|
|
353
|
+
.string()
|
|
354
|
+
.min(1)
|
|
355
|
+
.describe(
|
|
356
|
+
"Engram name: deep_recall | timeline | gather | equilibrium | tone_stabilizer (the service is the authority; an unknown name returns the accepted list)."
|
|
357
|
+
),
|
|
313
358
|
query: z.string().describe("What to retrieve, phrased like the user's need."),
|
|
314
359
|
...storeArg,
|
|
315
360
|
},
|
|
@@ -367,13 +412,26 @@ await server.connect(transport);
|
|
|
367
412
|
return inner?.(msg, extra);
|
|
368
413
|
};
|
|
369
414
|
}
|
|
370
|
-
console.error(
|
|
415
|
+
console.error(
|
|
416
|
+
`wontopos-mcp ${VERSION} ready (store: ${STORE}${STORE_FROM_ENV ? "" : " — FALLBACK: WONTOPOS_USER_ID is not set"}${READ_ONLY ? ", read-only" : ""})`
|
|
417
|
+
);
|
|
371
418
|
|
|
372
419
|
// Boot check (one metered stats call, after connect so host startup never waits):
|
|
373
420
|
// "ready" above is a claim — verify it. A wrong key can do nothing → exit loudly.
|
|
374
421
|
// A missing store is recoverable (the agent can create_store) → warn and serve.
|
|
375
422
|
mem.stats().then(
|
|
376
|
-
(s) =>
|
|
423
|
+
(s) => {
|
|
424
|
+
console.error(`wontopos-mcp: store "${STORE}" verified (${s?.total_memories ?? "?"} memories).`);
|
|
425
|
+
// Verified, but on the fallback store — the case a misconfiguration hides in.
|
|
426
|
+
// The seeded `default` always exists, so this check passes and the operator has
|
|
427
|
+
// no other signal that the store they configured is not the one in use.
|
|
428
|
+
if (!STORE_FROM_ENV) {
|
|
429
|
+
console.error(
|
|
430
|
+
'wontopos-mcp: WONTOPOS_USER_ID is not set, so this server is using the seeded "default" store. ' +
|
|
431
|
+
"If you meant a different store, set WONTOPOS_USER_ID in the MCP server's env and restart."
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
377
435
|
(e) => {
|
|
378
436
|
const status = e instanceof WosError ? e.status : 0;
|
|
379
437
|
if (status === 401) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wontopos-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"mcpName": "com.wontopos/mcp",
|
|
5
5
|
"description": "(beta) Wontopos (WOS) long-term memory as an MCP server — one memory across Claude Code, Claude Desktop, Cursor, ChatGPT, and your own agents.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
|
-
"wontopos": "^2.2.
|
|
35
|
+
"wontopos": "^2.2.22",
|
|
36
36
|
"zod": "^3.23.0"
|
|
37
37
|
}
|
|
38
38
|
}
|