ofw-mcp 2.0.5 → 2.0.6
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +85 -23
- package/dist/cache.js +49 -2
- package/dist/index.js +1 -1
- package/dist/sync.js +14 -7
- package/dist/tools/messages.js +26 -12
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.6"
|
|
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.
|
|
17
|
+
"version": "2.0.6",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Chris Chall"
|
|
20
20
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -31129,13 +31129,59 @@ function getMessage(id) {
|
|
|
31129
31129
|
function listMessages(opts) {
|
|
31130
31130
|
const { db } = openCache();
|
|
31131
31131
|
const offset = (opts.page - 1) * opts.size;
|
|
31132
|
+
const wheres = [];
|
|
31133
|
+
const params = [];
|
|
31134
|
+
if (opts.folder !== void 0) {
|
|
31135
|
+
wheres.push("folder = ?");
|
|
31136
|
+
params.push(opts.folder);
|
|
31137
|
+
}
|
|
31138
|
+
if (opts.since !== void 0) {
|
|
31139
|
+
wheres.push("sent_at >= ?");
|
|
31140
|
+
params.push(opts.since);
|
|
31141
|
+
}
|
|
31142
|
+
if (opts.until !== void 0) {
|
|
31143
|
+
wheres.push("sent_at < ?");
|
|
31144
|
+
params.push(opts.until);
|
|
31145
|
+
}
|
|
31146
|
+
if (opts.q !== void 0 && opts.q.length > 0) {
|
|
31147
|
+
const pattern = `%${opts.q}%`;
|
|
31148
|
+
wheres.push("(subject LIKE ? OR body LIKE ?)");
|
|
31149
|
+
params.push(pattern, pattern);
|
|
31150
|
+
}
|
|
31151
|
+
const where = wheres.length > 0 ? `WHERE ${wheres.join(" AND ")}` : "";
|
|
31152
|
+
params.push(opts.size, offset);
|
|
31132
31153
|
const rows = db.prepare(
|
|
31133
|
-
`SELECT * FROM messages
|
|
31154
|
+
`SELECT * FROM messages ${where}
|
|
31134
31155
|
ORDER BY sent_at DESC, id DESC
|
|
31135
31156
|
LIMIT ? OFFSET ?`
|
|
31136
|
-
).all(
|
|
31157
|
+
).all(...params);
|
|
31137
31158
|
return rows.map(rowFromDb);
|
|
31138
31159
|
}
|
|
31160
|
+
function countMessages(opts) {
|
|
31161
|
+
const { db } = openCache();
|
|
31162
|
+
const wheres = [];
|
|
31163
|
+
const params = [];
|
|
31164
|
+
if (opts.folder !== void 0) {
|
|
31165
|
+
wheres.push("folder = ?");
|
|
31166
|
+
params.push(opts.folder);
|
|
31167
|
+
}
|
|
31168
|
+
if (opts.since !== void 0) {
|
|
31169
|
+
wheres.push("sent_at >= ?");
|
|
31170
|
+
params.push(opts.since);
|
|
31171
|
+
}
|
|
31172
|
+
if (opts.until !== void 0) {
|
|
31173
|
+
wheres.push("sent_at < ?");
|
|
31174
|
+
params.push(opts.until);
|
|
31175
|
+
}
|
|
31176
|
+
if (opts.q !== void 0 && opts.q.length > 0) {
|
|
31177
|
+
const pattern = `%${opts.q}%`;
|
|
31178
|
+
wheres.push("(subject LIKE ? OR body LIKE ?)");
|
|
31179
|
+
params.push(pattern, pattern);
|
|
31180
|
+
}
|
|
31181
|
+
const where = wheres.length > 0 ? `WHERE ${wheres.join(" AND ")}` : "";
|
|
31182
|
+
const r = db.prepare(`SELECT COUNT(*) as n FROM messages ${where}`).get(...params);
|
|
31183
|
+
return r?.n ?? 0;
|
|
31184
|
+
}
|
|
31139
31185
|
function draftFromDb(r) {
|
|
31140
31186
|
return {
|
|
31141
31187
|
id: r.id,
|
|
@@ -31258,14 +31304,12 @@ async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
|
31258
31304
|
const list = await client2.request("GET", path);
|
|
31259
31305
|
const items = list.data ?? [];
|
|
31260
31306
|
if (items.length === 0) break;
|
|
31261
|
-
let
|
|
31307
|
+
let pageHadNewItem = false;
|
|
31262
31308
|
for (const item of items) {
|
|
31263
31309
|
if (newestId === null || item.id > newestId) newestId = item.id;
|
|
31264
31310
|
const existing = getMessage(item.id);
|
|
31265
|
-
if (existing)
|
|
31266
|
-
|
|
31267
|
-
continue;
|
|
31268
|
-
}
|
|
31311
|
+
if (existing) continue;
|
|
31312
|
+
pageHadNewItem = true;
|
|
31269
31313
|
const isInboxUnread = folder === "inbox" && item.showNeverViewed === true;
|
|
31270
31314
|
const shouldFetchBody = !isInboxUnread || opts.fetchUnreadBodies;
|
|
31271
31315
|
let body = null;
|
|
@@ -31298,7 +31342,7 @@ async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
|
31298
31342
|
upsertMessage(row);
|
|
31299
31343
|
synced++;
|
|
31300
31344
|
}
|
|
31301
|
-
if (
|
|
31345
|
+
if (!opts.deep && !pageHadNewItem) break;
|
|
31302
31346
|
page++;
|
|
31303
31347
|
}
|
|
31304
31348
|
setSyncState(folder, {
|
|
@@ -31349,12 +31393,16 @@ async function syncAll(client2, opts) {
|
|
|
31349
31393
|
for (const folder of folders) {
|
|
31350
31394
|
if (folder === "inbox") {
|
|
31351
31395
|
const r = await syncMessageFolder(client2, "inbox", ids.inbox, {
|
|
31352
|
-
fetchUnreadBodies: opts.fetchUnreadBodies ?? false
|
|
31396
|
+
fetchUnreadBodies: opts.fetchUnreadBodies ?? false,
|
|
31397
|
+
deep: opts.deep ?? false
|
|
31353
31398
|
});
|
|
31354
31399
|
synced.inbox = r.synced;
|
|
31355
31400
|
unreadInbox = r.unread;
|
|
31356
31401
|
} else if (folder === "sent") {
|
|
31357
|
-
const r = await syncMessageFolder(client2, "sent", ids.sent, {
|
|
31402
|
+
const r = await syncMessageFolder(client2, "sent", ids.sent, {
|
|
31403
|
+
fetchUnreadBodies: false,
|
|
31404
|
+
deep: opts.deep ?? false
|
|
31405
|
+
});
|
|
31358
31406
|
synced.sent = r.synced;
|
|
31359
31407
|
} else if (folder === "drafts") {
|
|
31360
31408
|
const r = await syncDrafts(client2, ids.drafts);
|
|
@@ -31375,32 +31423,44 @@ function registerMessageTools(server2, client2) {
|
|
|
31375
31423
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
31376
31424
|
});
|
|
31377
31425
|
server2.registerTool("ofw_list_messages", {
|
|
31378
|
-
description:
|
|
31426
|
+
description: "List messages from the local OurFamilyWizard cache. Supports filtering by folder, date range, and a substring query on subject+body. Pagination is offset-based but if you know what you want (a date range, a topic), prefer the filters over walking pages \u2014 the cache may have 1000+ messages. Call ofw_sync_messages first if the cache is empty or stale.",
|
|
31379
31427
|
annotations: { readOnlyHint: true },
|
|
31380
31428
|
inputSchema: {
|
|
31381
|
-
folderId: external_exports.string().describe('Folder name: "inbox" or "
|
|
31429
|
+
folderId: external_exports.string().describe('Folder name: "inbox", "sent", or "both" (default "both")').optional(),
|
|
31382
31430
|
page: external_exports.number().describe("Page number (default 1)").optional(),
|
|
31383
|
-
size: external_exports.number().describe("Messages per page (default 50)").optional()
|
|
31431
|
+
size: external_exports.number().describe("Messages per page (default 50)").optional(),
|
|
31432
|
+
since: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at >= since (inclusive)").optional(),
|
|
31433
|
+
until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive)").optional(),
|
|
31434
|
+
q: external_exports.string().describe("Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.").optional()
|
|
31384
31435
|
}
|
|
31385
31436
|
}, async (args) => {
|
|
31386
31437
|
const page = args.page ?? 1;
|
|
31387
31438
|
const size = args.size ?? 50;
|
|
31388
|
-
|
|
31389
|
-
|
|
31390
|
-
|
|
31439
|
+
const folderArg = args.folderId ?? "both";
|
|
31440
|
+
let folder;
|
|
31441
|
+
if (folderArg === "inbox") folder = "inbox";
|
|
31442
|
+
else if (folderArg === "sent") folder = "sent";
|
|
31443
|
+
else if (folderArg === "both") folder = void 0;
|
|
31391
31444
|
else {
|
|
31392
31445
|
return {
|
|
31393
31446
|
content: [{
|
|
31394
31447
|
type: "text",
|
|
31395
31448
|
text: JSON.stringify({
|
|
31396
31449
|
messages: [],
|
|
31397
|
-
note: '
|
|
31450
|
+
note: 'folderId must be "inbox", "sent", or "both". Numeric OFW folder IDs are not supported by the cache.'
|
|
31398
31451
|
}, null, 2)
|
|
31399
31452
|
}]
|
|
31400
31453
|
};
|
|
31401
31454
|
}
|
|
31402
|
-
const
|
|
31403
|
-
const
|
|
31455
|
+
const filter = { folder, since: args.since, until: args.until, q: args.q };
|
|
31456
|
+
const total = countMessages(filter);
|
|
31457
|
+
const messages = listMessages({ ...filter, page, size });
|
|
31458
|
+
const payload = { messages, total, page, size };
|
|
31459
|
+
if (total === 0) {
|
|
31460
|
+
payload.note = "No messages match these filters. If you expected results, check ofw_sync_messages was run, or relax the filters.";
|
|
31461
|
+
} else if (page * size < total) {
|
|
31462
|
+
payload.note = `Showing ${(page - 1) * size + 1}\u2013${(page - 1) * size + messages.length} of ${total}. Increase 'page' to see more, or narrow with since/until/q.`;
|
|
31463
|
+
}
|
|
31404
31464
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
31405
31465
|
});
|
|
31406
31466
|
server2.registerTool("ofw_get_message", {
|
|
@@ -31614,16 +31674,18 @@ ${text}` : text;
|
|
|
31614
31674
|
return { content: [{ type: "text", text: JSON.stringify(unread, null, 2) }] };
|
|
31615
31675
|
});
|
|
31616
31676
|
server2.registerTool("ofw_sync_messages", {
|
|
31617
|
-
description: "Sync messages from OurFamilyWizard into the local cache. Returns counts per folder and a list of unread inbox messages whose bodies were NOT fetched (to avoid mark-as-read on OFW). Call ofw_get_message(id) on those to read them.",
|
|
31677
|
+
description: "Sync messages from OurFamilyWizard into the local cache. Returns counts per folder and a list of unread inbox messages whose bodies were NOT fetched (to avoid mark-as-read on OFW). Call ofw_get_message(id) on those to read them. Pass deep:true to walk all OFW pages instead of stopping at the first all-cached page (use to backfill suspected gaps).",
|
|
31618
31678
|
annotations: { readOnlyHint: false },
|
|
31619
31679
|
inputSchema: {
|
|
31620
31680
|
folders: external_exports.array(external_exports.enum(["inbox", "sent", "drafts"])).describe("Folders to sync (default: all three)").optional(),
|
|
31621
|
-
fetchUnreadBodies: external_exports.boolean().describe("If true, also fetch bodies for unread inbox messages (will mark them as read on OFW). Default false.").optional()
|
|
31681
|
+
fetchUnreadBodies: external_exports.boolean().describe("If true, also fetch bodies for unread inbox messages (will mark them as read on OFW). Default false.").optional(),
|
|
31682
|
+
deep: external_exports.boolean().describe("If true, walk every OFW page until empty regardless of cache state. Use to backfill gaps. Default false.").optional()
|
|
31622
31683
|
}
|
|
31623
31684
|
}, async (args) => {
|
|
31624
31685
|
const result = await syncAll(client2, {
|
|
31625
31686
|
folders: args.folders,
|
|
31626
|
-
fetchUnreadBodies: args.fetchUnreadBodies
|
|
31687
|
+
fetchUnreadBodies: args.fetchUnreadBodies,
|
|
31688
|
+
deep: args.deep
|
|
31627
31689
|
});
|
|
31628
31690
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
31629
31691
|
});
|
|
@@ -31771,7 +31833,7 @@ process.emit = function(event, ...args) {
|
|
|
31771
31833
|
}
|
|
31772
31834
|
return originalEmit(event, ...args);
|
|
31773
31835
|
};
|
|
31774
|
-
var server = new McpServer({ name: "ofw", version: "2.0.
|
|
31836
|
+
var server = new McpServer({ name: "ofw", version: "2.0.6" });
|
|
31775
31837
|
registerUserTools(server, client);
|
|
31776
31838
|
registerMessageTools(server, client);
|
|
31777
31839
|
registerCalendarTools(server, client);
|
package/dist/cache.js
CHANGED
|
@@ -106,11 +106,58 @@ export function getMessage(id) {
|
|
|
106
106
|
export function listMessages(opts) {
|
|
107
107
|
const { db } = openCache();
|
|
108
108
|
const offset = (opts.page - 1) * opts.size;
|
|
109
|
-
const
|
|
109
|
+
const wheres = [];
|
|
110
|
+
const params = [];
|
|
111
|
+
if (opts.folder !== undefined) {
|
|
112
|
+
wheres.push('folder = ?');
|
|
113
|
+
params.push(opts.folder);
|
|
114
|
+
}
|
|
115
|
+
if (opts.since !== undefined) {
|
|
116
|
+
wheres.push('sent_at >= ?');
|
|
117
|
+
params.push(opts.since);
|
|
118
|
+
}
|
|
119
|
+
if (opts.until !== undefined) {
|
|
120
|
+
wheres.push('sent_at < ?');
|
|
121
|
+
params.push(opts.until);
|
|
122
|
+
}
|
|
123
|
+
if (opts.q !== undefined && opts.q.length > 0) {
|
|
124
|
+
const pattern = `%${opts.q}%`;
|
|
125
|
+
wheres.push('(subject LIKE ? OR body LIKE ?)');
|
|
126
|
+
params.push(pattern, pattern);
|
|
127
|
+
}
|
|
128
|
+
const where = wheres.length > 0 ? `WHERE ${wheres.join(' AND ')}` : '';
|
|
129
|
+
params.push(opts.size, offset);
|
|
130
|
+
const rows = db.prepare(`SELECT * FROM messages ${where}
|
|
110
131
|
ORDER BY sent_at DESC, id DESC
|
|
111
|
-
LIMIT ? OFFSET ?`).all(
|
|
132
|
+
LIMIT ? OFFSET ?`).all(...params);
|
|
112
133
|
return rows.map(rowFromDb);
|
|
113
134
|
}
|
|
135
|
+
export function countMessages(opts) {
|
|
136
|
+
const { db } = openCache();
|
|
137
|
+
const wheres = [];
|
|
138
|
+
const params = [];
|
|
139
|
+
if (opts.folder !== undefined) {
|
|
140
|
+
wheres.push('folder = ?');
|
|
141
|
+
params.push(opts.folder);
|
|
142
|
+
}
|
|
143
|
+
if (opts.since !== undefined) {
|
|
144
|
+
wheres.push('sent_at >= ?');
|
|
145
|
+
params.push(opts.since);
|
|
146
|
+
}
|
|
147
|
+
if (opts.until !== undefined) {
|
|
148
|
+
wheres.push('sent_at < ?');
|
|
149
|
+
params.push(opts.until);
|
|
150
|
+
}
|
|
151
|
+
if (opts.q !== undefined && opts.q.length > 0) {
|
|
152
|
+
const pattern = `%${opts.q}%`;
|
|
153
|
+
wheres.push('(subject LIKE ? OR body LIKE ?)');
|
|
154
|
+
params.push(pattern, pattern);
|
|
155
|
+
}
|
|
156
|
+
const where = wheres.length > 0 ? `WHERE ${wheres.join(' AND ')}` : '';
|
|
157
|
+
const r = db.prepare(`SELECT COUNT(*) as n FROM messages ${where}`)
|
|
158
|
+
.get(...params);
|
|
159
|
+
return r?.n ?? 0;
|
|
160
|
+
}
|
|
114
161
|
function draftFromDb(r) {
|
|
115
162
|
return {
|
|
116
163
|
id: r.id,
|
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.
|
|
20
|
+
const server = new McpServer({ name: 'ofw', version: '2.0.6' });
|
|
21
21
|
registerUserTools(server, client);
|
|
22
22
|
registerMessageTools(server, client);
|
|
23
23
|
registerCalendarTools(server, client);
|
package/dist/sync.js
CHANGED
|
@@ -34,15 +34,14 @@ export async function syncMessageFolder(client, folder, folderId, opts) {
|
|
|
34
34
|
const items = list.data ?? [];
|
|
35
35
|
if (items.length === 0)
|
|
36
36
|
break;
|
|
37
|
-
let
|
|
37
|
+
let pageHadNewItem = false;
|
|
38
38
|
for (const item of items) {
|
|
39
39
|
if (newestId === null || item.id > newestId)
|
|
40
40
|
newestId = item.id;
|
|
41
41
|
const existing = getMessage(item.id);
|
|
42
|
-
if (existing)
|
|
43
|
-
pageSawKnownItem = true;
|
|
42
|
+
if (existing)
|
|
44
43
|
continue;
|
|
45
|
-
|
|
44
|
+
pageHadNewItem = true;
|
|
46
45
|
const isInboxUnread = folder === 'inbox' && item.showNeverViewed === true;
|
|
47
46
|
const shouldFetchBody = !isInboxUnread || opts.fetchUnreadBodies;
|
|
48
47
|
let body = null;
|
|
@@ -76,8 +75,12 @@ export async function syncMessageFolder(client, folder, folderId, opts) {
|
|
|
76
75
|
upsertMessage(row);
|
|
77
76
|
synced++;
|
|
78
77
|
}
|
|
79
|
-
//
|
|
80
|
-
|
|
78
|
+
// Stop heuristic: a page with no new items means we've reached cached
|
|
79
|
+
// history (OFW returns date-desc). A page with even ONE new item could
|
|
80
|
+
// mean there are more new items on the next page that we haven't seen
|
|
81
|
+
// yet — keep walking. With `deep: true`, walk every page until OFW
|
|
82
|
+
// returns an empty page (used to backfill suspected gaps).
|
|
83
|
+
if (!opts.deep && !pageHadNewItem)
|
|
81
84
|
break;
|
|
82
85
|
page++;
|
|
83
86
|
}
|
|
@@ -129,12 +132,16 @@ export async function syncAll(client, opts) {
|
|
|
129
132
|
if (folder === 'inbox') {
|
|
130
133
|
const r = await syncMessageFolder(client, 'inbox', ids.inbox, {
|
|
131
134
|
fetchUnreadBodies: opts.fetchUnreadBodies ?? false,
|
|
135
|
+
deep: opts.deep ?? false,
|
|
132
136
|
});
|
|
133
137
|
synced.inbox = r.synced;
|
|
134
138
|
unreadInbox = r.unread;
|
|
135
139
|
}
|
|
136
140
|
else if (folder === 'sent') {
|
|
137
|
-
const r = await syncMessageFolder(client, 'sent', ids.sent, {
|
|
141
|
+
const r = await syncMessageFolder(client, 'sent', ids.sent, {
|
|
142
|
+
fetchUnreadBodies: false,
|
|
143
|
+
deep: opts.deep ?? false,
|
|
144
|
+
});
|
|
138
145
|
synced.sent = r.synced;
|
|
139
146
|
}
|
|
140
147
|
else if (folder === 'drafts') {
|
package/dist/tools/messages.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { syncAll } from '../sync.js';
|
|
3
|
-
import { listMessages, listDrafts, getMessage, upsertMessage, upsertDraft, deleteDraft, findLatestReplyTip, } from '../cache.js';
|
|
3
|
+
import { listMessages, countMessages, listDrafts, getMessage, upsertMessage, upsertDraft, deleteDraft, findLatestReplyTip, } from '../cache.js';
|
|
4
4
|
export function registerMessageTools(server, client) {
|
|
5
5
|
server.registerTool('ofw_list_message_folders', {
|
|
6
6
|
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.',
|
|
@@ -10,36 +10,48 @@ export function registerMessageTools(server, client) {
|
|
|
10
10
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
11
11
|
});
|
|
12
12
|
server.registerTool('ofw_list_messages', {
|
|
13
|
-
description: 'List messages from the local OurFamilyWizard cache.
|
|
13
|
+
description: 'List messages from the local OurFamilyWizard cache. Supports filtering by folder, date range, and a substring query on subject+body. Pagination is offset-based but if you know what you want (a date range, a topic), prefer the filters over walking pages — the cache may have 1000+ messages. Call ofw_sync_messages first if the cache is empty or stale.',
|
|
14
14
|
annotations: { readOnlyHint: true },
|
|
15
15
|
inputSchema: {
|
|
16
|
-
folderId: z.string().describe('Folder name: "inbox" or "
|
|
16
|
+
folderId: z.string().describe('Folder name: "inbox", "sent", or "both" (default "both")').optional(),
|
|
17
17
|
page: z.number().describe('Page number (default 1)').optional(),
|
|
18
18
|
size: z.number().describe('Messages per page (default 50)').optional(),
|
|
19
|
+
since: z.string().describe('ISO date or datetime — only messages with sent_at >= since (inclusive)').optional(),
|
|
20
|
+
until: z.string().describe('ISO date or datetime — only messages with sent_at < until (exclusive)').optional(),
|
|
21
|
+
q: z.string().describe('Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.').optional(),
|
|
19
22
|
},
|
|
20
23
|
}, async (args) => {
|
|
21
24
|
const page = args.page ?? 1;
|
|
22
25
|
const size = args.size ?? 50;
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
const folderArg = args.folderId ?? 'both';
|
|
27
|
+
let folder;
|
|
28
|
+
if (folderArg === 'inbox')
|
|
25
29
|
folder = 'inbox';
|
|
26
|
-
else if (
|
|
30
|
+
else if (folderArg === 'sent')
|
|
27
31
|
folder = 'sent';
|
|
32
|
+
else if (folderArg === 'both')
|
|
33
|
+
folder = undefined;
|
|
28
34
|
else {
|
|
29
35
|
return {
|
|
30
36
|
content: [{
|
|
31
37
|
type: 'text',
|
|
32
38
|
text: JSON.stringify({
|
|
33
39
|
messages: [],
|
|
34
|
-
note: '
|
|
40
|
+
note: 'folderId must be "inbox", "sent", or "both". Numeric OFW folder IDs are not supported by the cache.',
|
|
35
41
|
}, null, 2),
|
|
36
42
|
}],
|
|
37
43
|
};
|
|
38
44
|
}
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
const filter = { folder, since: args.since, until: args.until, q: args.q };
|
|
46
|
+
const total = countMessages(filter);
|
|
47
|
+
const messages = listMessages({ ...filter, page, size });
|
|
48
|
+
const payload = { messages, total, page, size };
|
|
49
|
+
if (total === 0) {
|
|
50
|
+
payload.note = 'No messages match these filters. If you expected results, check ofw_sync_messages was run, or relax the filters.';
|
|
51
|
+
}
|
|
52
|
+
else if (page * size < total) {
|
|
53
|
+
payload.note = `Showing ${(page - 1) * size + 1}–${(page - 1) * size + messages.length} of ${total}. Increase 'page' to see more, or narrow with since/until/q.`;
|
|
54
|
+
}
|
|
43
55
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
44
56
|
});
|
|
45
57
|
server.registerTool('ofw_get_message', {
|
|
@@ -246,16 +258,18 @@ export function registerMessageTools(server, client) {
|
|
|
246
258
|
return { content: [{ type: 'text', text: JSON.stringify(unread, null, 2) }] };
|
|
247
259
|
});
|
|
248
260
|
server.registerTool('ofw_sync_messages', {
|
|
249
|
-
description: 'Sync messages from OurFamilyWizard into the local cache. Returns counts per folder and a list of unread inbox messages whose bodies were NOT fetched (to avoid mark-as-read on OFW). Call ofw_get_message(id) on those to read them.',
|
|
261
|
+
description: 'Sync messages from OurFamilyWizard into the local cache. Returns counts per folder and a list of unread inbox messages whose bodies were NOT fetched (to avoid mark-as-read on OFW). Call ofw_get_message(id) on those to read them. Pass deep:true to walk all OFW pages instead of stopping at the first all-cached page (use to backfill suspected gaps).',
|
|
250
262
|
annotations: { readOnlyHint: false },
|
|
251
263
|
inputSchema: {
|
|
252
264
|
folders: z.array(z.enum(['inbox', 'sent', 'drafts'])).describe('Folders to sync (default: all three)').optional(),
|
|
253
265
|
fetchUnreadBodies: z.boolean().describe('If true, also fetch bodies for unread inbox messages (will mark them as read on OFW). Default false.').optional(),
|
|
266
|
+
deep: z.boolean().describe('If true, walk every OFW page until empty regardless of cache state. Use to backfill gaps. Default false.').optional(),
|
|
254
267
|
},
|
|
255
268
|
}, async (args) => {
|
|
256
269
|
const result = await syncAll(client, {
|
|
257
270
|
folders: args.folders,
|
|
258
271
|
fetchUnreadBodies: args.fetchUnreadBodies,
|
|
272
|
+
deep: args.deep,
|
|
259
273
|
});
|
|
260
274
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
261
275
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
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.
|
|
9
|
+
"version": "2.0.6",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.0.
|
|
14
|
+
"version": "2.0.6",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|