ofw-mcp 2.0.10 → 2.0.11

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "OurFamilyWizard tools for Claude Code",
9
- "version": "2.0.10"
9
+ "version": "2.0.11"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -14,7 +14,7 @@
14
14
  "displayName": "OurFamilyWizard",
15
15
  "source": "./",
16
16
  "description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
17
- "version": "2.0.10",
17
+ "version": "2.0.11",
18
18
  "author": {
19
19
  "name": "Chris Chall"
20
20
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ofw",
3
3
  "displayName": "OurFamilyWizard",
4
- "version": "2.0.10",
4
+ "version": "2.0.11",
5
5
  "description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall"
package/dist/bundle.js CHANGED
@@ -31379,12 +31379,14 @@ function listAttachmentsForMessage(messageId) {
31379
31379
  function upsertAttachmentForMessage(input) {
31380
31380
  const { db } = openCache();
31381
31381
  const existing = db.prepare("SELECT message_ids_json FROM attachments WHERE file_id = ?").get(input.fileId);
31382
+ const prior = existing ? JSON.parse(existing.message_ids_json) : [];
31382
31383
  let messageIds;
31383
- if (existing) {
31384
- const arr = JSON.parse(existing.message_ids_json);
31385
- messageIds = arr.includes(input.messageId) ? arr : [...arr, input.messageId];
31384
+ if (input.messageId === 0) {
31385
+ messageIds = prior;
31386
+ } else if (prior.includes(input.messageId)) {
31387
+ messageIds = prior;
31386
31388
  } else {
31387
- messageIds = [input.messageId];
31389
+ messageIds = [...prior, input.messageId];
31388
31390
  }
31389
31391
  db.prepare(
31390
31392
  `INSERT INTO attachments (
@@ -31621,6 +31623,13 @@ var MIME_BY_EXT = {
31621
31623
  function mimeFromName(name) {
31622
31624
  return MIME_BY_EXT[extname(name).toLowerCase()] ?? "application/octet-stream";
31623
31625
  }
31626
+ function listDataHintsAtFiles(listData) {
31627
+ if (typeof listData !== "object" || listData === null) return false;
31628
+ const ld = listData;
31629
+ if (typeof ld.files === "number") return ld.files > 0;
31630
+ if (Array.isArray(ld.files)) return ld.files.length > 0;
31631
+ return false;
31632
+ }
31624
31633
  function registerMessageTools(server2, client2) {
31625
31634
  server2.registerTool("ofw_list_message_folders", {
31626
31635
  description: "List OurFamilyWizard message folders (inbox, sent, etc.) and their unread counts. Returns folder IDs needed to call ofw_list_messages. Does NOT return message content.",
@@ -31680,7 +31689,17 @@ function registerMessageTools(server2, client2) {
31680
31689
  const id = Number(args.messageId);
31681
31690
  const cached2 = getMessage(id);
31682
31691
  if (cached2 && cached2.body !== null) {
31683
- const attachments2 = listAttachmentsForMessage(id);
31692
+ let attachments2 = listAttachmentsForMessage(id);
31693
+ if (attachments2.length === 0 && listDataHintsAtFiles(cached2.listData)) {
31694
+ try {
31695
+ const detail2 = await client2.request("GET", `/pub/v3/messages/${id}`);
31696
+ if (Array.isArray(detail2.files) && detail2.files.length > 0) {
31697
+ await fetchAttachmentMetaForMessage(client2, id, detail2.files);
31698
+ attachments2 = listAttachmentsForMessage(id);
31699
+ }
31700
+ } catch {
31701
+ }
31702
+ }
31684
31703
  return { content: [{ type: "text", text: JSON.stringify({ ...cached2, attachments: attachments2 }, null, 2) }] };
31685
31704
  }
31686
31705
  const detail = await client2.request("GET", `/pub/v3/messages/${encodeURIComponent(args.messageId)}`);
@@ -32161,7 +32180,7 @@ process.emit = function(event, ...args) {
32161
32180
  }
32162
32181
  return originalEmit(event, ...args);
32163
32182
  };
32164
- var server = new McpServer({ name: "ofw", version: "2.0.10" });
32183
+ var server = new McpServer({ name: "ofw", version: "2.0.11" });
32165
32184
  registerUserTools(server, client);
32166
32185
  registerMessageTools(server, client);
32167
32186
  registerCalendarTools(server, client);
package/dist/cache.js CHANGED
@@ -295,13 +295,19 @@ export function upsertAttachmentForMessage(input) {
295
295
  const { db } = openCache();
296
296
  const existing = db.prepare('SELECT message_ids_json FROM attachments WHERE file_id = ?')
297
297
  .get(input.fileId);
298
+ // messageId === 0 is the "metadata-only, not yet linked to a message"
299
+ // sentinel used by upload-without-send and download-by-id. Don't
300
+ // pollute the array with it — leave the list empty / unchanged.
301
+ const prior = existing ? JSON.parse(existing.message_ids_json) : [];
298
302
  let messageIds;
299
- if (existing) {
300
- const arr = JSON.parse(existing.message_ids_json);
301
- messageIds = arr.includes(input.messageId) ? arr : [...arr, input.messageId];
303
+ if (input.messageId === 0) {
304
+ messageIds = prior;
305
+ }
306
+ else if (prior.includes(input.messageId)) {
307
+ messageIds = prior;
302
308
  }
303
309
  else {
304
- messageIds = [input.messageId];
310
+ messageIds = [...prior, input.messageId];
305
311
  }
306
312
  db.prepare(`INSERT INTO attachments (
307
313
  file_id, file_name, label, mime_type, size_bytes,
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import { registerMessageTools } from './tools/messages.js';
17
17
  import { registerCalendarTools } from './tools/calendar.js';
18
18
  import { registerExpenseTools } from './tools/expenses.js';
19
19
  import { registerJournalTools } from './tools/journal.js';
20
- const server = new McpServer({ name: 'ofw', version: '2.0.10' });
20
+ const server = new McpServer({ name: 'ofw', version: '2.0.11' });
21
21
  registerUserTools(server, client);
22
22
  registerMessageTools(server, client);
23
23
  registerCalendarTools(server, client);
@@ -31,6 +31,20 @@ const MIME_BY_EXT = {
31
31
  function mimeFromName(name) {
32
32
  return MIME_BY_EXT[extname(name).toLowerCase()] ?? 'application/octet-stream';
33
33
  }
34
+ // The list endpoint payload (cached as `listData`) reports attachments via
35
+ // `files: <count>` (a number) — the actual fileIds only appear on the detail
36
+ // endpoint as `files: [number, ...]`. Some intermediate shapes return an
37
+ // array on the list too. Treat any of those as "this message has files".
38
+ function listDataHintsAtFiles(listData) {
39
+ if (typeof listData !== 'object' || listData === null)
40
+ return false;
41
+ const ld = listData;
42
+ if (typeof ld.files === 'number')
43
+ return ld.files > 0;
44
+ if (Array.isArray(ld.files))
45
+ return ld.files.length > 0;
46
+ return false;
47
+ }
34
48
  export function registerMessageTools(server, client) {
35
49
  server.registerTool('ofw_list_message_folders', {
36
50
  description: 'List OurFamilyWizard message folders (inbox, sent, etc.) and their unread counts. Returns folder IDs needed to call ofw_list_messages. Does NOT return message content.',
@@ -94,7 +108,26 @@ export function registerMessageTools(server, client) {
94
108
  const id = Number(args.messageId);
95
109
  const cached = getMessage(id);
96
110
  if (cached && cached.body !== null) {
97
- const attachments = listAttachmentsForMessage(id);
111
+ let attachments = listAttachmentsForMessage(id);
112
+ // Lazy attachment backfill. The list-endpoint payload (stored in
113
+ // listData) hints at attachments via `files: <count>` but doesn't
114
+ // expose the fileIds — those live only on /pub/v3/messages/{id}.
115
+ // For messages bodied before attachment caching existed, the
116
+ // attachments table is empty even though OFW has files. Re-hit
117
+ // detail to harvest fileIds (idempotent: body is already cached so
118
+ // OFW state isn't changing).
119
+ if (attachments.length === 0 && listDataHintsAtFiles(cached.listData)) {
120
+ try {
121
+ const detail = await client.request('GET', `/pub/v3/messages/${id}`);
122
+ if (Array.isArray(detail.files) && detail.files.length > 0) {
123
+ await fetchAttachmentMetaForMessage(client, id, detail.files);
124
+ attachments = listAttachmentsForMessage(id);
125
+ }
126
+ }
127
+ catch {
128
+ // Backfill is best-effort. Fall through with whatever we have.
129
+ }
130
+ }
98
131
  return { content: [{ type: 'text', text: JSON.stringify({ ...cached, attachments }, null, 2) }] };
99
132
  }
100
133
  const detail = await client.request('GET', `/pub/v3/messages/${encodeURIComponent(args.messageId)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.0.10",
3
+ "version": "2.0.11",
4
4
  "mcpName": "io.github.chrischall/ofw-mcp",
5
5
  "description": "OurFamilyWizard MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/ofw-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.0.10",
9
+ "version": "2.0.11",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.0.10",
14
+ "version": "2.0.11",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },