@llmnesia/mcp 0.2.6 → 0.2.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.
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  ));
39
39
 
40
40
  // src/version.ts
41
- var VERSION = true ? "0.2.6" : "0.0.0-dev";
41
+ var VERSION = true ? "0.2.7" : "0.0.0-dev";
42
42
 
43
43
  export {
44
44
  __require,
@@ -3,7 +3,7 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-YRK2DRGG.js";
6
+ } from "./chunk-CMQHGLAD.js";
7
7
 
8
8
  // src/config.ts
9
9
  import { readFileSync } from "fs";
@@ -47,7 +47,7 @@ function expandHome(p) {
47
47
  import { join as join2 } from "path";
48
48
  import { readdir } from "fs/promises";
49
49
  import { createHash } from "crypto";
50
- var INDEX_FORMAT_VERSION = 3;
50
+ var INDEX_FORMAT_VERSION = 4;
51
51
  function indexDbFilename(version) {
52
52
  return version === 1 ? "llmnesia.db" : `llmnesia-v${version}.db`;
53
53
  }
@@ -208,6 +208,8 @@ function canonicalConversationDoc(c) {
208
208
  assignIfDefined(out, "origin", c.origin);
209
209
  assignIfDefined(out, "accountId", c.accountId);
210
210
  assignIfDefined(out, "accountLabel", c.accountLabel);
211
+ if (c.models && c.models.length > 0) out.models = [...c.models];
212
+ assignIfDefined(out, "modelMetadataUpdatedAt", c.modelMetadataUpdatedAt);
211
213
  if (c.summary) {
212
214
  out.summary = {
213
215
  text: c.summary.text,
@@ -227,6 +229,7 @@ function canonicalMessageDoc(m) {
227
229
  text: m.text
228
230
  };
229
231
  assignIfDefined(out, "formattedText", m.formattedText);
232
+ assignIfDefined(out, "model", m.model);
230
233
  out.createdAt = m.createdAt;
231
234
  out.updatedAt = m.updatedAt;
232
235
  out.contentHash = m.contentHash;
@@ -904,6 +907,9 @@ function coerceMessage(docId, value) {
904
907
  if (typeof m.formattedText === "string") {
905
908
  message.formattedText = m.formattedText;
906
909
  }
910
+ if (typeof m.model === "string" && m.model.trim()) {
911
+ message.model = m.model.trim();
912
+ }
907
913
  if (typeof m.platformMessageId === "string") {
908
914
  message.platformMessageId = m.platformMessageId;
909
915
  } else if (m.platformMessageId === null) {
@@ -1008,7 +1014,8 @@ CREATE TABLE IF NOT EXISTS conversations (
1008
1014
  preview TEXT NOT NULL,
1009
1015
  pinned INTEGER NOT NULL,
1010
1016
  accountId TEXT,
1011
- accountLabel TEXT
1017
+ accountLabel TEXT,
1018
+ models TEXT
1012
1019
  );
1013
1020
  CREATE INDEX IF NOT EXISTS idx_conversations_platform ON conversations(platform);
1014
1021
  CREATE INDEX IF NOT EXISTS idx_conversations_createdAt ON conversations(createdAt);
@@ -1082,8 +1089,8 @@ var SearchIndex = class {
1082
1089
  deleteConversation: this.db.prepare("DELETE FROM conversations WHERE docId = ?"),
1083
1090
  insertConversation: this.db.prepare(
1084
1091
  `INSERT INTO conversations
1085
- (docId, platform, title, url, createdAt, updatedAt, messageCount, preview, pinned, accountId, accountLabel)
1086
- VALUES (@docId, @platform, @title, @url, @createdAt, @updatedAt, @messageCount, @preview, @pinned, @accountId, @accountLabel)`
1092
+ (docId, platform, title, url, createdAt, updatedAt, messageCount, preview, pinned, accountId, accountLabel, models)
1093
+ VALUES (@docId, @platform, @title, @url, @createdAt, @updatedAt, @messageCount, @preview, @pinned, @accountId, @accountLabel, @models)`
1087
1094
  ),
1088
1095
  deleteMemoryFts: this.db.prepare("DELETE FROM memories_fts WHERE memoryId = ?"),
1089
1096
  insertMemoryFts: this.db.prepare(
@@ -1127,14 +1134,16 @@ var SearchIndex = class {
1127
1134
  const orderBy = sort === "oldest" ? "createdAt ASC, updatedAt ASC" : sort === "newest" ? "createdAt DESC, updatedAt DESC" : "updatedAt DESC, createdAt DESC";
1128
1135
  const rows = this.db.prepare(
1129
1136
  `SELECT docId, platform, title, url, createdAt, updatedAt, messageCount, preview, pinned,
1130
- accountId, accountLabel
1137
+ accountId, accountLabel, models
1131
1138
  FROM conversations ORDER BY ${orderBy} LIMIT ?`
1132
1139
  ).all(limit);
1133
1140
  return rows.map((r) => {
1134
- const { accountId, accountLabel, ...rest } = r;
1141
+ const { accountId, accountLabel, models, ...rest } = r;
1135
1142
  const entry = { ...rest, pinned: r.pinned === 1 };
1136
1143
  if (accountId) entry.accountId = accountId;
1137
1144
  if (accountLabel) entry.accountLabel = accountLabel;
1145
+ const parsedModels = parseModels(models);
1146
+ if (parsedModels.length > 0) entry.models = parsedModels;
1138
1147
  return entry;
1139
1148
  });
1140
1149
  }
@@ -1221,7 +1230,8 @@ var SearchIndex = class {
1221
1230
  preview: conversation.preview ?? "",
1222
1231
  pinned: conversation.pinned ? 1 : 0,
1223
1232
  accountId: conversation.accountId ?? null,
1224
- accountLabel: conversation.accountLabel ?? null
1233
+ accountLabel: conversation.accountLabel ?? null,
1234
+ models: conversation.models?.length ? JSON.stringify(conversation.models) : null
1225
1235
  });
1226
1236
  }
1227
1237
  insertFtsRow(docId, msgIndex, role, text) {
@@ -1286,7 +1296,7 @@ var SearchIndex = class {
1286
1296
  const sql = `
1287
1297
  SELECT
1288
1298
  c.docId, c.platform, c.title, c.url, c.createdAt, c.updatedAt,
1289
- c.messageCount, c.preview, c.pinned, c.accountId, c.accountLabel,
1299
+ c.messageCount, c.preview, c.pinned, c.accountId, c.accountLabel, c.models,
1290
1300
  m.msgIndex AS msgIndex, m.role AS role,
1291
1301
  bm25(messages_fts) AS bm25
1292
1302
  FROM messages_fts
@@ -1322,6 +1332,8 @@ var SearchIndex = class {
1322
1332
  };
1323
1333
  if (row.accountId) hit.accountId = row.accountId;
1324
1334
  if (row.accountLabel) hit.accountLabel = row.accountLabel;
1335
+ const models = parseModels(row.models);
1336
+ if (models.length > 0) hit.models = models;
1325
1337
  hits.push(hit);
1326
1338
  if (hits.length >= limit) {
1327
1339
  break;
@@ -1402,6 +1414,15 @@ function normalizeMatchRole(role) {
1402
1414
  }
1403
1415
  return role === "assistant" || role === "user" || role === "system" ? role : "unknown";
1404
1416
  }
1417
+ function parseModels(value) {
1418
+ if (!value) return [];
1419
+ try {
1420
+ const parsed = JSON.parse(value);
1421
+ return Array.isArray(parsed) ? parsed.filter((model) => typeof model === "string" && model.trim().length > 0) : [];
1422
+ } catch {
1423
+ return [];
1424
+ }
1425
+ }
1405
1426
 
1406
1427
  // src/storageMaintenance.ts
1407
1428
  import { promises as fs4 } from "fs";
@@ -17,13 +17,13 @@ import {
17
17
  runtimeBinDir,
18
18
  staleIndexFiles,
19
19
  uninstallRuntime
20
- } from "./chunk-HL5QE57W.js";
20
+ } from "./chunk-O34A5U2L.js";
21
21
  import {
22
22
  VERSION,
23
23
  __commonJS,
24
24
  __require,
25
25
  __toESM
26
- } from "./chunk-YRK2DRGG.js";
26
+ } from "./chunk-CMQHGLAD.js";
27
27
 
28
28
  // ../node_modules/isexe/windows.js
29
29
  var require_windows = __commonJS({
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-YRK2DRGG.js";
6
+ } from "./chunk-CMQHGLAD.js";
7
7
 
8
8
  // src/nodeDownload.ts
9
9
  var NODE_DOWNLOAD_URLS = {
@@ -141,7 +141,7 @@ async function bootstrap() {
141
141
  await guideUnsupportedNodeVersion(process.versions.node);
142
142
  return 1;
143
143
  }
144
- const { main } = await import("./cli-DRB4DVH3.js");
144
+ const { main } = await import("./cli-XQUMZ3BU.js");
145
145
  return main();
146
146
  }
147
147
  bootstrap().then((code) => {
Binary file
package/dist/mcp.js CHANGED
@@ -12,13 +12,13 @@ import {
12
12
  resolveCorpusDir,
13
13
  runAutomaticMaintenance,
14
14
  scheduleSelfUpdate
15
- } from "./chunk-HL5QE57W.js";
15
+ } from "./chunk-O34A5U2L.js";
16
16
  import {
17
17
  VERSION,
18
18
  __commonJS,
19
19
  __export,
20
20
  __toESM
21
- } from "./chunk-YRK2DRGG.js";
21
+ } from "./chunk-CMQHGLAD.js";
22
22
 
23
23
  // ../node_modules/ajv/dist/compile/codegen/code.js
24
24
  var require_code = __commonJS({
@@ -21678,19 +21678,26 @@ var LazyResource = class {
21678
21678
  function jsonResult(payload) {
21679
21679
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
21680
21680
  }
21681
- var SOURCE_LINK_INSTRUCTION = "When answering from these conversations, always include a Sources section with a clickable Markdown link to every chat you relied on. Link its `sourceUrl`: this is the original chat URL when available, otherwise its local LLMnesia Viewer URL. Do this automatically; do not wait for the user to ask for links.";
21681
+ var SOURCE_LINK_INSTRUCTION = "When answering from these conversations, always include a Sources section with a clickable Markdown link to every chat you relied on. Link its `sourceUrl`: this is the original chat URL when available, otherwise an HTTPS hand-off that opens the local LLMnesia Viewer without sending the conversation id to the website. Do this automatically; do not wait for the user to ask for links.";
21682
21682
  var STORE_EXTENSION_ID = "leekfgbdojiaabifbjbbgiiclannjdkf";
21683
21683
  var EXTENSION_ID_RE = /^[a-p]{32}$/;
21684
+ var VIEWER_HANDOFF_URL = "https://llmnesia.com/open";
21684
21685
  function viewerExtensionId() {
21685
21686
  const configured = process.env.LLMNESIA_VIEWER_EXTENSION_ID?.trim() ?? readNativeHostViewerExtensionId();
21686
21687
  return configured && EXTENSION_ID_RE.test(configured) ? configured : STORE_EXTENSION_ID;
21687
21688
  }
21688
- function viewerUrl(docId) {
21689
- return `chrome-extension://${viewerExtensionId()}/viewer.html?docId=${encodeURIComponent(docId)}`;
21689
+ function viewerSourceUrl(docId, extensionId) {
21690
+ const fragment = new URLSearchParams({ docId, extensionId });
21691
+ return `${VIEWER_HANDOFF_URL}#${fragment.toString()}`;
21690
21692
  }
21691
21693
  function sourceLinks(docId, originalUrl) {
21692
- const localViewerUrl = viewerUrl(docId);
21693
- return originalUrl ? { viewerUrl: localViewerUrl, sourceUrl: originalUrl, sourceKind: "original" } : { viewerUrl: localViewerUrl, sourceUrl: localViewerUrl, sourceKind: "viewer" };
21694
+ const extensionId = viewerExtensionId();
21695
+ const localViewerUrl = `chrome-extension://${extensionId}/viewer.html?docId=${encodeURIComponent(docId)}`;
21696
+ return originalUrl ? { viewerUrl: localViewerUrl, sourceUrl: originalUrl, sourceKind: "original" } : {
21697
+ viewerUrl: localViewerUrl,
21698
+ sourceUrl: viewerSourceUrl(docId, extensionId),
21699
+ sourceKind: "viewer"
21700
+ };
21694
21701
  }
21695
21702
  function sourcedJsonResult(payload) {
21696
21703
  return jsonResult({ responseInstruction: SOURCE_LINK_INSTRUCTION, ...payload });
@@ -21757,14 +21764,14 @@ async function runServe() {
21757
21764
  const server = new McpServer(
21758
21765
  { name: "llmnesia", version: VERSION },
21759
21766
  {
21760
- instructions: "LLMnesia is the user's searchable archive of past AI chats. Whenever the user asks about 'my chats', 'my conversations', or anything they previously said, discussed, decided, or worked on, use LLMnesia before built-in task/thread lists, local file scans, or memory, unless they explicitly ask only about Codex tasks. Use list_conversations with sort='oldest' for their earliest chat, search_conversations to find relevant prior chats, get_conversation to read a full transcript, and list_recent to see what they worked on lately. Search and list results carry a `corpus` block saying when the corpus last received data; if a chat the user expects is missing, check corpus_status before concluding it does not exist. Whenever you answer using LLMnesia, always include a Sources section with clickable links to the chats you used. Link each result's `sourceUrl`, which is its original URL when available or its local LLMnesia Viewer URL otherwise; never wait for the user to ask."
21767
+ instructions: "LLMnesia is the user's searchable archive of past AI chats. Whenever the user asks about 'my chats', 'my conversations', or anything they previously said, discussed, decided, or worked on, use LLMnesia before built-in task/thread lists, local file scans, or memory, unless they explicitly ask only about Codex tasks. Use list_conversations with sort='oldest' for their earliest chat, search_conversations to find relevant prior chats, get_conversation to read a full transcript, and list_recent to see what they worked on lately. Search and list results carry a `corpus` block saying when the corpus last received data; if a chat the user expects is missing, check corpus_status before concluding it does not exist. Whenever you answer using LLMnesia, always include a Sources section with clickable links to the chats you used. Link each result's `sourceUrl`, which is its original URL when available or a clickable HTTPS hand-off to its local LLMnesia Viewer otherwise; never wait for the user to ask."
21761
21768
  }
21762
21769
  );
21763
21770
  server.registerTool(
21764
21771
  "search_conversations",
21765
21772
  {
21766
21773
  title: "Search conversations",
21767
- description: "Full-text search across the user's past AI conversations. Call this whenever the user asks what they said, thought, discussed, decided, or worked on previously \u2014 even if the question doesn't say 'search' \u2014 rather than answering from conversation memory alone or claiming no record exists. Returns matching conversations with a highlighted snippet, ranked by relevance. Defaults to recall-first matching (any term); pass match_mode to answer questions where a wrong hit matters, such as whether or how often something was discussed. Every result includes `sourceUrl`: the original chat URL when available, otherwise its local LLMnesia Viewer URL. Always link the chats used in the answer.",
21774
+ description: "Full-text search across the user's past AI conversations. Call this whenever the user asks what they said, thought, discussed, decided, or worked on previously \u2014 even if the question doesn't say 'search' \u2014 rather than answering from conversation memory alone or claiming no record exists. Returns matching conversations with a highlighted snippet, ranked by relevance. Defaults to recall-first matching (any term); pass match_mode to answer questions where a wrong hit matters, such as whether or how often something was discussed. Every result includes `sourceUrl`: the original chat URL when available, otherwise a clickable HTTPS hand-off to its local LLMnesia Viewer. Always link the chats used in the answer.",
21768
21775
  inputSchema: {
21769
21776
  query: external_exports.string().describe("Search terms (natural words; FTS operators are not needed)."),
21770
21777
  match_mode: external_exports.enum(["any", "all", "phrase"]).optional().describe(
@@ -21813,6 +21820,7 @@ async function runServe() {
21813
21820
  };
21814
21821
  if (h.accountId) entry.accountId = h.accountId;
21815
21822
  if (h.accountLabel) entry.accountLabel = h.accountLabel;
21823
+ if (h.models?.length) entry.models = h.models;
21816
21824
  return entry;
21817
21825
  })
21818
21826
  });
@@ -21822,7 +21830,7 @@ async function runServe() {
21822
21830
  "get_conversation",
21823
21831
  {
21824
21832
  title: "Get conversation",
21825
- description: "Return the full transcript of one conversation by its id (the `id` from search results). The conversation includes `sourceUrl`: its original-chat URL when available, otherwise its local LLMnesia Viewer URL. Always link it in an answer based on this transcript.",
21833
+ description: "Return the full transcript of one conversation by its id (the `id` from search results). The conversation includes `sourceUrl`: its original-chat URL when available, otherwise a clickable HTTPS hand-off to its local LLMnesia Viewer. Always link it in an answer based on this transcript.",
21826
21834
  inputSchema: {
21827
21835
  id: external_exports.string().describe("The conversation id (docId), e.g. 'claude:abc-123'.")
21828
21836
  },
@@ -21846,6 +21854,7 @@ async function runServe() {
21846
21854
  messages: record2.messages.map((m) => ({
21847
21855
  index: m.msgIndex,
21848
21856
  role: m.role,
21857
+ ...m.model ? { model: m.model } : {},
21849
21858
  // Prefer the block-formatted copy; fall back to the search-collapsed text.
21850
21859
  text: m.formattedText ?? m.text
21851
21860
  }))
@@ -21856,7 +21865,7 @@ async function runServe() {
21856
21865
  "list_recent",
21857
21866
  {
21858
21867
  title: "List recent conversations",
21859
- description: "List the user's most recently updated conversations (title + summary + source URL), newest first. `sourceUrl` is the original chat URL when available, otherwise its local LLMnesia Viewer URL. Always link the chats used in the answer.",
21868
+ description: "List the user's most recently updated conversations (title + summary + source URL), newest first. `sourceUrl` is the original chat URL when available, otherwise a clickable HTTPS hand-off to its local LLMnesia Viewer. Always link the chats used in the answer.",
21860
21869
  inputSchema: {
21861
21870
  limit: external_exports.number().int().min(1).max(100).optional().describe("How many to return (default 10).")
21862
21871
  },
@@ -21887,6 +21896,7 @@ async function runServe() {
21887
21896
  };
21888
21897
  if (e.accountId) entry.accountId = e.accountId;
21889
21898
  if (e.accountLabel) entry.accountLabel = e.accountLabel;
21899
+ if (e.models?.length) entry.models = e.models;
21890
21900
  return entry;
21891
21901
  })
21892
21902
  });
@@ -21896,7 +21906,7 @@ async function runServe() {
21896
21906
  "list_conversations",
21897
21907
  {
21898
21908
  title: "List conversations by date",
21899
- description: "List conversation metadata in chronological order. Use sort='oldest' for the user's earliest or oldest chat, sort='newest' for most recently created chats, and sort='recent' for most recently updated chats. This is the complete corpus chronology, so do not scan LLMnesia's private files with shell commands. Entries include `sourceUrl`: the original chat URL when available, otherwise the local LLMnesia Viewer URL. Always link the chats used in the answer.",
21909
+ description: "List conversation metadata in chronological order. Use sort='oldest' for the user's earliest or oldest chat, sort='newest' for most recently created chats, and sort='recent' for most recently updated chats. This is the complete corpus chronology, so do not scan LLMnesia's private files with shell commands. Entries include `sourceUrl`: the original chat URL when available, otherwise a clickable HTTPS hand-off to the local LLMnesia Viewer. Always link the chats used in the answer.",
21900
21910
  inputSchema: {
21901
21911
  sort: external_exports.enum(["recent", "oldest", "newest"]).optional().describe("Chronology: recent (default, updated), oldest (created), or newest (created)."),
21902
21912
  limit: external_exports.number().int().min(1).max(100).optional().describe("How many to return (default 10).")
@@ -21930,6 +21940,7 @@ async function runServe() {
21930
21940
  };
21931
21941
  if (e.accountId) entry.accountId = e.accountId;
21932
21942
  if (e.accountLabel) entry.accountLabel = e.accountLabel;
21943
+ if (e.models?.length) entry.models = e.models;
21933
21944
  return entry;
21934
21945
  })
21935
21946
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmnesia/mcp",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "comment:mcpName": "The MCP Registry's ownership marker: it must equal server.json's `name`, and the namespace must match how you authenticate with mcp-publisher. `com.llmnesia/...` requires DNS auth (a TXT record on llmnesia.com) — see docs/MCP_INSTALLER_BOARD.md.",
package/server.json CHANGED
@@ -3,13 +3,13 @@
3
3
  "name": "com.llmnesia/mcp",
4
4
  "title": "LLMnesia",
5
5
  "description": "Search your AI chat history from Claude, Cursor, Codex, and other MCP clients.",
6
- "version": "0.2.6",
6
+ "version": "0.2.7",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "registryBaseUrl": "https://registry.npmjs.org",
11
11
  "identifier": "@llmnesia/mcp",
12
- "version": "0.2.6",
12
+ "version": "0.2.7",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  }