ofw-mcp 2.0.10 → 2.0.12
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 +208 -215
- package/dist/cache.js +24 -30
- package/dist/client.js +38 -60
- package/dist/config.js +17 -4
- package/dist/index.js +1 -1
- package/dist/sync.js +25 -32
- package/dist/tools/_shared.js +22 -0
- package/dist/tools/calendar.js +5 -4
- package/dist/tools/expenses.js +4 -3
- package/dist/tools/journal.js +3 -2
- package/dist/tools/messages.js +132 -102
- package/dist/tools/user.js +3 -2
- package/package.json +4 -4
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -30880,74 +30880,53 @@ function readVar(key) {
|
|
|
30880
30880
|
return trimmed;
|
|
30881
30881
|
}
|
|
30882
30882
|
var BASE_URL = "https://ofw.ourfamilywizard.com";
|
|
30883
|
-
var
|
|
30883
|
+
var OFW_PROTOCOL_HEADERS = {
|
|
30884
30884
|
"ofw-client": "WebApplication",
|
|
30885
|
-
"ofw-version": "1.0.0"
|
|
30886
|
-
Accept: "application/json",
|
|
30887
|
-
"Content-Type": "application/json"
|
|
30885
|
+
"ofw-version": "1.0.0"
|
|
30888
30886
|
};
|
|
30887
|
+
function parseContentDispositionFilename(cd) {
|
|
30888
|
+
const extMatch = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(cd);
|
|
30889
|
+
if (extMatch) {
|
|
30890
|
+
const raw = extMatch[1].trim().replace(/^"|"$/g, "");
|
|
30891
|
+
try {
|
|
30892
|
+
return decodeURIComponent(raw);
|
|
30893
|
+
} catch {
|
|
30894
|
+
return raw;
|
|
30895
|
+
}
|
|
30896
|
+
}
|
|
30897
|
+
const m = /filename="?([^";]+)"?/i.exec(cd);
|
|
30898
|
+
return m ? m[1] : null;
|
|
30899
|
+
}
|
|
30889
30900
|
var OFWClient = class {
|
|
30890
30901
|
token = null;
|
|
30891
30902
|
tokenExpiry = null;
|
|
30892
30903
|
async request(method, path, body) {
|
|
30893
30904
|
await this.ensureAuthenticated();
|
|
30894
|
-
|
|
30905
|
+
const response = await this.fetchWithRetry(method, path, body, "application/json", false);
|
|
30906
|
+
const text = await response.text();
|
|
30907
|
+
return text ? JSON.parse(text) : null;
|
|
30895
30908
|
}
|
|
30896
30909
|
/** Like `request`, but returns the raw bytes plus Content-Type/-Disposition metadata. */
|
|
30897
30910
|
async requestBinary(method, path) {
|
|
30898
30911
|
await this.ensureAuthenticated();
|
|
30899
|
-
|
|
30900
|
-
}
|
|
30901
|
-
async doRequestBinary(method, path, isRetry) {
|
|
30902
|
-
const headers = {
|
|
30903
|
-
"ofw-client": "WebApplication",
|
|
30904
|
-
"ofw-version": "1.0.0",
|
|
30905
|
-
Accept: "application/octet-stream",
|
|
30906
|
-
Authorization: `Bearer ${this.token}`
|
|
30907
|
-
};
|
|
30908
|
-
const response = await fetch(`${BASE_URL}${path}`, { method, headers });
|
|
30909
|
-
if (response.status === 401 && !isRetry) {
|
|
30910
|
-
this.token = null;
|
|
30911
|
-
this.tokenExpiry = null;
|
|
30912
|
-
await this.ensureAuthenticated();
|
|
30913
|
-
return this.doRequestBinary(method, path, true);
|
|
30914
|
-
}
|
|
30915
|
-
if (response.status === 429 && !isRetry) {
|
|
30916
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
30917
|
-
return this.doRequestBinary(method, path, true);
|
|
30918
|
-
}
|
|
30919
|
-
if (!response.ok) {
|
|
30920
|
-
throw new Error(`OFW API error: ${response.status} ${response.statusText} for ${method} ${path}`);
|
|
30921
|
-
}
|
|
30922
|
-
const buf = Buffer.from(await response.arrayBuffer());
|
|
30923
|
-
const cd = response.headers.get("content-disposition") ?? "";
|
|
30924
|
-
let suggestedFileName = null;
|
|
30925
|
-
const extMatch = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(cd);
|
|
30926
|
-
if (extMatch) {
|
|
30927
|
-
try {
|
|
30928
|
-
suggestedFileName = decodeURIComponent(extMatch[1].trim().replace(/^"|"$/g, ""));
|
|
30929
|
-
} catch {
|
|
30930
|
-
suggestedFileName = extMatch[1];
|
|
30931
|
-
}
|
|
30932
|
-
} else {
|
|
30933
|
-
const m = /filename="?([^";]+)"?/i.exec(cd);
|
|
30934
|
-
if (m) suggestedFileName = m[1];
|
|
30935
|
-
}
|
|
30912
|
+
const response = await this.fetchWithRetry(method, path, void 0, "application/octet-stream", false);
|
|
30936
30913
|
return {
|
|
30937
|
-
body:
|
|
30914
|
+
body: Buffer.from(await response.arrayBuffer()),
|
|
30938
30915
|
contentType: response.headers.get("content-type"),
|
|
30939
|
-
suggestedFileName
|
|
30916
|
+
suggestedFileName: parseContentDispositionFilename(response.headers.get("content-disposition") ?? "")
|
|
30940
30917
|
};
|
|
30941
30918
|
}
|
|
30942
|
-
|
|
30919
|
+
// Single fetch+retry scaffold for both JSON and binary callers. Handles
|
|
30920
|
+
// 401 (re-auth and replay once), 429 (wait 2s and replay once), and
|
|
30921
|
+
// turns any other non-2xx into a thrown Error.
|
|
30922
|
+
async fetchWithRetry(method, path, body, accept, isRetry) {
|
|
30943
30923
|
const isFormData = body instanceof FormData;
|
|
30944
30924
|
const headers = {
|
|
30945
|
-
|
|
30946
|
-
|
|
30947
|
-
Accept: "application/json",
|
|
30925
|
+
...OFW_PROTOCOL_HEADERS,
|
|
30926
|
+
Accept: accept,
|
|
30948
30927
|
Authorization: `Bearer ${this.token}`
|
|
30949
30928
|
};
|
|
30950
|
-
if (!isFormData) headers["Content-Type"] = "application/json";
|
|
30929
|
+
if (body !== void 0 && !isFormData) headers["Content-Type"] = "application/json";
|
|
30951
30930
|
const response = await fetch(`${BASE_URL}${path}`, {
|
|
30952
30931
|
method,
|
|
30953
30932
|
headers,
|
|
@@ -30957,22 +30936,19 @@ var OFWClient = class {
|
|
|
30957
30936
|
this.token = null;
|
|
30958
30937
|
this.tokenExpiry = null;
|
|
30959
30938
|
await this.ensureAuthenticated();
|
|
30960
|
-
return this.
|
|
30939
|
+
return this.fetchWithRetry(method, path, body, accept, true);
|
|
30961
30940
|
}
|
|
30962
30941
|
if (response.status === 429) {
|
|
30963
30942
|
if (!isRetry) {
|
|
30964
30943
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
30965
|
-
return this.
|
|
30944
|
+
return this.fetchWithRetry(method, path, body, accept, true);
|
|
30966
30945
|
}
|
|
30967
30946
|
throw new Error("Rate limited by OFW API");
|
|
30968
30947
|
}
|
|
30969
30948
|
if (!response.ok) {
|
|
30970
|
-
throw new Error(
|
|
30971
|
-
`OFW API error: ${response.status} ${response.statusText} for ${method} ${path}`
|
|
30972
|
-
);
|
|
30949
|
+
throw new Error(`OFW API error: ${response.status} ${response.statusText} for ${method} ${path}`);
|
|
30973
30950
|
}
|
|
30974
|
-
|
|
30975
|
-
return text ? JSON.parse(text) : null;
|
|
30951
|
+
return response;
|
|
30976
30952
|
}
|
|
30977
30953
|
async ensureAuthenticated() {
|
|
30978
30954
|
if (!this.isTokenExpiredSoon()) return;
|
|
@@ -30985,7 +30961,7 @@ var OFWClient = class {
|
|
|
30985
30961
|
throw new Error("OFW_USERNAME and OFW_PASSWORD must be set");
|
|
30986
30962
|
}
|
|
30987
30963
|
const initResponse = await fetch(`${BASE_URL}/ofw/login.form`, {
|
|
30988
|
-
headers: {
|
|
30964
|
+
headers: { ...OFW_PROTOCOL_HEADERS },
|
|
30989
30965
|
redirect: "manual"
|
|
30990
30966
|
});
|
|
30991
30967
|
const setCookie = initResponse.headers.get("set-cookie") ?? "";
|
|
@@ -30993,7 +30969,8 @@ var OFWClient = class {
|
|
|
30993
30969
|
const response = await fetch(`${BASE_URL}/ofw/login`, {
|
|
30994
30970
|
method: "POST",
|
|
30995
30971
|
headers: {
|
|
30996
|
-
...
|
|
30972
|
+
...OFW_PROTOCOL_HEADERS,
|
|
30973
|
+
Accept: "application/json",
|
|
30997
30974
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
30998
30975
|
...sessionCookie ? { Cookie: sessionCookie } : {}
|
|
30999
30976
|
},
|
|
@@ -31023,6 +31000,26 @@ var OFWClient = class {
|
|
|
31023
31000
|
};
|
|
31024
31001
|
var client = new OFWClient();
|
|
31025
31002
|
|
|
31003
|
+
// src/tools/_shared.ts
|
|
31004
|
+
import { isAbsolute, join as join2, resolve } from "node:path";
|
|
31005
|
+
function jsonResponse(payload) {
|
|
31006
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
31007
|
+
}
|
|
31008
|
+
function textResponse(text) {
|
|
31009
|
+
return { content: [{ type: "text", text }] };
|
|
31010
|
+
}
|
|
31011
|
+
function mapRecipients(items) {
|
|
31012
|
+
return (items ?? []).map((r) => ({
|
|
31013
|
+
userId: r.user?.id ?? 0,
|
|
31014
|
+
name: r.user?.name ?? "",
|
|
31015
|
+
viewedAt: r.viewed?.dateTime ?? null
|
|
31016
|
+
}));
|
|
31017
|
+
}
|
|
31018
|
+
function expandPath(p) {
|
|
31019
|
+
const expanded = p.startsWith("~/") ? join2(process.env.HOME ?? "", p.slice(2)) : p;
|
|
31020
|
+
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
31021
|
+
}
|
|
31022
|
+
|
|
31026
31023
|
// src/tools/user.ts
|
|
31027
31024
|
function registerUserTools(server2, client2) {
|
|
31028
31025
|
server2.registerTool("ofw_get_profile", {
|
|
@@ -31030,14 +31027,14 @@ function registerUserTools(server2, client2) {
|
|
|
31030
31027
|
annotations: { readOnlyHint: true }
|
|
31031
31028
|
}, async () => {
|
|
31032
31029
|
const data = await client2.request("GET", "/pub/v2/profiles");
|
|
31033
|
-
return
|
|
31030
|
+
return jsonResponse(data);
|
|
31034
31031
|
});
|
|
31035
31032
|
server2.registerTool("ofw_get_notifications", {
|
|
31036
31033
|
description: "Get OurFamilyWizard dashboard summary: unread message count, upcoming events, outstanding expenses. Note: updates your last-seen status.",
|
|
31037
31034
|
annotations: { readOnlyHint: false }
|
|
31038
31035
|
}, async () => {
|
|
31039
31036
|
const data = await client2.request("GET", "/pub/v1/users/useraccountstatus");
|
|
31040
|
-
return
|
|
31037
|
+
return jsonResponse(data);
|
|
31041
31038
|
});
|
|
31042
31039
|
}
|
|
31043
31040
|
|
|
@@ -31049,7 +31046,7 @@ import { dirname as dirname2 } from "node:path";
|
|
|
31049
31046
|
// src/config.ts
|
|
31050
31047
|
import { createHash } from "node:crypto";
|
|
31051
31048
|
import { homedir } from "node:os";
|
|
31052
|
-
import { join as
|
|
31049
|
+
import { join as join3 } from "node:path";
|
|
31053
31050
|
function readUsername() {
|
|
31054
31051
|
const raw = process.env.OFW_USERNAME;
|
|
31055
31052
|
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
@@ -31060,19 +31057,22 @@ function readUsername() {
|
|
|
31060
31057
|
function getCacheDir() {
|
|
31061
31058
|
const override = process.env.OFW_CACHE_DIR;
|
|
31062
31059
|
if (override && override.trim().length > 0) return override.trim();
|
|
31063
|
-
return
|
|
31060
|
+
return join3(homedir(), ".cache", "ofw-mcp");
|
|
31064
31061
|
}
|
|
31065
31062
|
function getCacheDbPath() {
|
|
31066
31063
|
const username = readUsername();
|
|
31067
31064
|
const hash2 = createHash("sha256").update(username).digest("hex").slice(0, 16);
|
|
31068
|
-
return
|
|
31065
|
+
return join3(getCacheDir(), `${hash2}.db`);
|
|
31069
31066
|
}
|
|
31070
31067
|
function getAttachmentsDir() {
|
|
31071
31068
|
const override = process.env.OFW_ATTACHMENTS_DIR;
|
|
31072
31069
|
if (override && override.trim().length > 0) return override.trim();
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31070
|
+
return join3(homedir(), "Downloads", "ofw-mcp");
|
|
31071
|
+
}
|
|
31072
|
+
function getDefaultInlineAttachments() {
|
|
31073
|
+
const raw = process.env.OFW_INLINE_ATTACHMENTS;
|
|
31074
|
+
if (typeof raw !== "string") return false;
|
|
31075
|
+
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
|
31076
31076
|
}
|
|
31077
31077
|
|
|
31078
31078
|
// src/cache.ts
|
|
@@ -31209,9 +31209,7 @@ function getMessage(id) {
|
|
|
31209
31209
|
const r = db.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
31210
31210
|
return r ? rowFromDb(r) : null;
|
|
31211
31211
|
}
|
|
31212
|
-
function
|
|
31213
|
-
const { db } = openCache();
|
|
31214
|
-
const offset = (opts.page - 1) * opts.size;
|
|
31212
|
+
function buildMessageFilter(opts) {
|
|
31215
31213
|
const wheres = [];
|
|
31216
31214
|
const params = [];
|
|
31217
31215
|
if (opts.folder !== void 0) {
|
|
@@ -31231,37 +31229,25 @@ function listMessages(opts) {
|
|
|
31231
31229
|
wheres.push("(subject LIKE ? OR body LIKE ?)");
|
|
31232
31230
|
params.push(pattern, pattern);
|
|
31233
31231
|
}
|
|
31234
|
-
|
|
31235
|
-
|
|
31232
|
+
return {
|
|
31233
|
+
where: wheres.length > 0 ? `WHERE ${wheres.join(" AND ")}` : "",
|
|
31234
|
+
params
|
|
31235
|
+
};
|
|
31236
|
+
}
|
|
31237
|
+
function listMessages(opts) {
|
|
31238
|
+
const { db } = openCache();
|
|
31239
|
+
const { where, params } = buildMessageFilter(opts);
|
|
31240
|
+
const offset = (opts.page - 1) * opts.size;
|
|
31236
31241
|
const rows = db.prepare(
|
|
31237
31242
|
`SELECT * FROM messages ${where}
|
|
31238
31243
|
ORDER BY sent_at DESC, id DESC
|
|
31239
31244
|
LIMIT ? OFFSET ?`
|
|
31240
|
-
).all(...params);
|
|
31245
|
+
).all(...params, opts.size, offset);
|
|
31241
31246
|
return rows.map(rowFromDb);
|
|
31242
31247
|
}
|
|
31243
31248
|
function countMessages(opts) {
|
|
31244
31249
|
const { db } = openCache();
|
|
31245
|
-
const
|
|
31246
|
-
const params = [];
|
|
31247
|
-
if (opts.folder !== void 0) {
|
|
31248
|
-
wheres.push("folder = ?");
|
|
31249
|
-
params.push(opts.folder);
|
|
31250
|
-
}
|
|
31251
|
-
if (opts.since !== void 0) {
|
|
31252
|
-
wheres.push("sent_at >= ?");
|
|
31253
|
-
params.push(opts.since);
|
|
31254
|
-
}
|
|
31255
|
-
if (opts.until !== void 0) {
|
|
31256
|
-
wheres.push("sent_at < ?");
|
|
31257
|
-
params.push(opts.until);
|
|
31258
|
-
}
|
|
31259
|
-
if (opts.q !== void 0 && opts.q.length > 0) {
|
|
31260
|
-
const pattern = `%${opts.q}%`;
|
|
31261
|
-
wheres.push("(subject LIKE ? OR body LIKE ?)");
|
|
31262
|
-
params.push(pattern, pattern);
|
|
31263
|
-
}
|
|
31264
|
-
const where = wheres.length > 0 ? `WHERE ${wheres.join(" AND ")}` : "";
|
|
31250
|
+
const { where, params } = buildMessageFilter(opts);
|
|
31265
31251
|
const r = db.prepare(`SELECT COUNT(*) as n FROM messages ${where}`).get(...params);
|
|
31266
31252
|
return r?.n ?? 0;
|
|
31267
31253
|
}
|
|
@@ -31379,12 +31365,14 @@ function listAttachmentsForMessage(messageId) {
|
|
|
31379
31365
|
function upsertAttachmentForMessage(input) {
|
|
31380
31366
|
const { db } = openCache();
|
|
31381
31367
|
const existing = db.prepare("SELECT message_ids_json FROM attachments WHERE file_id = ?").get(input.fileId);
|
|
31368
|
+
const prior = existing ? JSON.parse(existing.message_ids_json) : [];
|
|
31382
31369
|
let messageIds;
|
|
31383
|
-
if (
|
|
31384
|
-
|
|
31385
|
-
|
|
31370
|
+
if (input.messageId === 0) {
|
|
31371
|
+
messageIds = prior;
|
|
31372
|
+
} else if (prior.includes(input.messageId)) {
|
|
31373
|
+
messageIds = prior;
|
|
31386
31374
|
} else {
|
|
31387
|
-
messageIds = [input.messageId];
|
|
31375
|
+
messageIds = [...prior, input.messageId];
|
|
31388
31376
|
}
|
|
31389
31377
|
db.prepare(
|
|
31390
31378
|
`INSERT INTO attachments (
|
|
@@ -31418,24 +31406,24 @@ function markAttachmentDownloaded(fileId, path) {
|
|
|
31418
31406
|
}
|
|
31419
31407
|
|
|
31420
31408
|
// src/sync.ts
|
|
31421
|
-
async function
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
|
|
31428
|
-
|
|
31429
|
-
|
|
31430
|
-
|
|
31431
|
-
|
|
31432
|
-
});
|
|
31433
|
-
} catch {
|
|
31434
|
-
}
|
|
31409
|
+
async function fetchAttachmentMeta(client2, fileId, messageId) {
|
|
31410
|
+
const meta3 = await client2.request("GET", `/pub/v1/myfiles/${fileId}`);
|
|
31411
|
+
upsertAttachmentForMessage({
|
|
31412
|
+
fileId: meta3.fileId ?? fileId,
|
|
31413
|
+
fileName: meta3.fileName ?? `file-${fileId}`,
|
|
31414
|
+
label: meta3.label ?? meta3.fileName ?? `file-${fileId}`,
|
|
31415
|
+
mimeType: meta3.fileType ?? "application/octet-stream",
|
|
31416
|
+
sizeBytes: typeof meta3.fileSize === "number" ? meta3.fileSize : null,
|
|
31417
|
+
metadata: meta3,
|
|
31418
|
+
messageId
|
|
31419
|
+
});
|
|
31435
31420
|
}
|
|
31436
31421
|
async function fetchAttachmentMetaForMessage(client2, messageId, fileIds) {
|
|
31437
31422
|
for (const fid of fileIds) {
|
|
31438
|
-
|
|
31423
|
+
try {
|
|
31424
|
+
await fetchAttachmentMeta(client2, fid, messageId);
|
|
31425
|
+
} catch {
|
|
31426
|
+
}
|
|
31439
31427
|
}
|
|
31440
31428
|
}
|
|
31441
31429
|
async function resolveFolderIds(client2) {
|
|
@@ -31457,13 +31445,6 @@ async function resolveFolderIds(client2) {
|
|
|
31457
31445
|
setMeta("drafts_folder_id", ids.drafts);
|
|
31458
31446
|
return ids;
|
|
31459
31447
|
}
|
|
31460
|
-
function recipientsFromList(item) {
|
|
31461
|
-
return (item.recipients ?? []).map((r) => ({
|
|
31462
|
-
userId: r.user.id,
|
|
31463
|
-
name: r.user.name,
|
|
31464
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31465
|
-
}));
|
|
31466
|
-
}
|
|
31467
31448
|
async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
31468
31449
|
let page = 1;
|
|
31469
31450
|
let synced = 0;
|
|
@@ -31506,7 +31487,7 @@ async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
|
31506
31487
|
subject: item.subject ?? "(no subject)",
|
|
31507
31488
|
fromUser: item.from?.name ?? "",
|
|
31508
31489
|
sentAt: item.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31509
|
-
recipients:
|
|
31490
|
+
recipients: mapRecipients(item.recipients),
|
|
31510
31491
|
body,
|
|
31511
31492
|
fetchedBodyAt,
|
|
31512
31493
|
replyToId: null,
|
|
@@ -31546,11 +31527,7 @@ async function syncDrafts(client2, draftsFolderId) {
|
|
|
31546
31527
|
id: item.id,
|
|
31547
31528
|
subject: detail.subject ?? item.subject ?? "(no subject)",
|
|
31548
31529
|
body: detail.body ?? "",
|
|
31549
|
-
recipients: (item.recipients
|
|
31550
|
-
userId: r.user?.id ?? 0,
|
|
31551
|
-
name: r.user?.name ?? "",
|
|
31552
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31553
|
-
})),
|
|
31530
|
+
recipients: mapRecipients(item.recipients),
|
|
31554
31531
|
replyToId: item.replyToId ?? null,
|
|
31555
31532
|
modifiedAt,
|
|
31556
31533
|
listData: item
|
|
@@ -31593,7 +31570,7 @@ async function syncAll(client2, opts) {
|
|
|
31593
31570
|
|
|
31594
31571
|
// src/tools/messages.ts
|
|
31595
31572
|
import { mkdirSync as mkdirSync2, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
31596
|
-
import { basename, dirname as dirname3, extname, join as
|
|
31573
|
+
import { basename, dirname as dirname3, extname, join as join4 } from "node:path";
|
|
31597
31574
|
var MIME_BY_EXT = {
|
|
31598
31575
|
".pdf": "application/pdf",
|
|
31599
31576
|
".png": "image/png",
|
|
@@ -31621,13 +31598,20 @@ var MIME_BY_EXT = {
|
|
|
31621
31598
|
function mimeFromName(name) {
|
|
31622
31599
|
return MIME_BY_EXT[extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
31623
31600
|
}
|
|
31601
|
+
function listDataHintsAtFiles(listData) {
|
|
31602
|
+
if (typeof listData !== "object" || listData === null) return false;
|
|
31603
|
+
const ld = listData;
|
|
31604
|
+
if (typeof ld.files === "number") return ld.files > 0;
|
|
31605
|
+
if (Array.isArray(ld.files)) return ld.files.length > 0;
|
|
31606
|
+
return false;
|
|
31607
|
+
}
|
|
31624
31608
|
function registerMessageTools(server2, client2) {
|
|
31625
31609
|
server2.registerTool("ofw_list_message_folders", {
|
|
31626
31610
|
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.",
|
|
31627
31611
|
annotations: { readOnlyHint: true }
|
|
31628
31612
|
}, async () => {
|
|
31629
31613
|
const data = await client2.request("GET", "/pub/v1/messageFolders?includeFolderCounts=true");
|
|
31630
|
-
return
|
|
31614
|
+
return jsonResponse(data);
|
|
31631
31615
|
});
|
|
31632
31616
|
server2.registerTool("ofw_list_messages", {
|
|
31633
31617
|
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.",
|
|
@@ -31649,15 +31633,10 @@ function registerMessageTools(server2, client2) {
|
|
|
31649
31633
|
else if (folderArg === "sent") folder = "sent";
|
|
31650
31634
|
else if (folderArg === "both") folder = void 0;
|
|
31651
31635
|
else {
|
|
31652
|
-
return {
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
messages: [],
|
|
31657
|
-
note: 'folderId must be "inbox", "sent", or "both". Numeric OFW folder IDs are not supported by the cache.'
|
|
31658
|
-
}, null, 2)
|
|
31659
|
-
}]
|
|
31660
|
-
};
|
|
31636
|
+
return jsonResponse({
|
|
31637
|
+
messages: [],
|
|
31638
|
+
note: 'folderId must be "inbox", "sent", or "both". Numeric OFW folder IDs are not supported by the cache.'
|
|
31639
|
+
});
|
|
31661
31640
|
}
|
|
31662
31641
|
const filter = { folder, since: args.since, until: args.until, q: args.q };
|
|
31663
31642
|
const total = countMessages(filter);
|
|
@@ -31668,7 +31647,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31668
31647
|
} else if (page * size < total) {
|
|
31669
31648
|
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.`;
|
|
31670
31649
|
}
|
|
31671
|
-
return
|
|
31650
|
+
return jsonResponse(payload);
|
|
31672
31651
|
});
|
|
31673
31652
|
server2.registerTool("ofw_get_message", {
|
|
31674
31653
|
description: "Get a single OurFamilyWizard message by ID. Reads from local cache when available; otherwise fetches from OFW (which will mark unread inbox messages as read on OFW).",
|
|
@@ -31680,15 +31659,20 @@ function registerMessageTools(server2, client2) {
|
|
|
31680
31659
|
const id = Number(args.messageId);
|
|
31681
31660
|
const cached2 = getMessage(id);
|
|
31682
31661
|
if (cached2 && cached2.body !== null) {
|
|
31683
|
-
|
|
31684
|
-
|
|
31662
|
+
let attachments2 = listAttachmentsForMessage(id);
|
|
31663
|
+
if (attachments2.length === 0 && listDataHintsAtFiles(cached2.listData)) {
|
|
31664
|
+
try {
|
|
31665
|
+
const detail2 = await client2.request("GET", `/pub/v3/messages/${id}`);
|
|
31666
|
+
if (Array.isArray(detail2.files) && detail2.files.length > 0) {
|
|
31667
|
+
await fetchAttachmentMetaForMessage(client2, id, detail2.files);
|
|
31668
|
+
attachments2 = listAttachmentsForMessage(id);
|
|
31669
|
+
}
|
|
31670
|
+
} catch {
|
|
31671
|
+
}
|
|
31672
|
+
}
|
|
31673
|
+
return jsonResponse({ ...cached2, attachments: attachments2 });
|
|
31685
31674
|
}
|
|
31686
31675
|
const detail = await client2.request("GET", `/pub/v3/messages/${encodeURIComponent(args.messageId)}`);
|
|
31687
|
-
const recipients = (detail.recipients ?? []).map((r) => ({
|
|
31688
|
-
userId: r.user.id,
|
|
31689
|
-
name: r.user.name,
|
|
31690
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31691
|
-
}));
|
|
31692
31676
|
const folder = cached2?.folder ?? "inbox";
|
|
31693
31677
|
const row = {
|
|
31694
31678
|
id: detail.id,
|
|
@@ -31696,7 +31680,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31696
31680
|
subject: detail.subject,
|
|
31697
31681
|
fromUser: detail.from?.name ?? "",
|
|
31698
31682
|
sentAt: detail.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31699
|
-
recipients,
|
|
31683
|
+
recipients: mapRecipients(detail.recipients),
|
|
31700
31684
|
body: detail.body ?? "",
|
|
31701
31685
|
fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31702
31686
|
replyToId: cached2?.replyToId ?? null,
|
|
@@ -31708,7 +31692,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31708
31692
|
await fetchAttachmentMetaForMessage(client2, detail.id, detail.files);
|
|
31709
31693
|
}
|
|
31710
31694
|
const attachments = listAttachmentsForMessage(detail.id);
|
|
31711
|
-
return
|
|
31695
|
+
return jsonResponse({ ...row, attachments });
|
|
31712
31696
|
});
|
|
31713
31697
|
server2.registerTool("ofw_send_message", {
|
|
31714
31698
|
description: "Send a message via OurFamilyWizard. If sending from a draft, pass draftId to delete the draft after sending. If replyToId is provided, the cache may rewrite it to the latest reply in the same thread (a note is included in the response when this happens). Attach files by passing their fileIds (from ofw_upload_attachment) in myFileIDs.",
|
|
@@ -31745,18 +31729,13 @@ function registerMessageTools(server2, client2) {
|
|
|
31745
31729
|
replyToId: resolvedReplyTo
|
|
31746
31730
|
});
|
|
31747
31731
|
if (data && typeof data.id === "number") {
|
|
31748
|
-
const recipients = (data.recipients ?? []).map((r) => ({
|
|
31749
|
-
userId: r.user.id,
|
|
31750
|
-
name: r.user.name,
|
|
31751
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31752
|
-
}));
|
|
31753
31732
|
const row = {
|
|
31754
31733
|
id: data.id,
|
|
31755
31734
|
folder: "sent",
|
|
31756
31735
|
subject: data.subject ?? args.subject,
|
|
31757
31736
|
fromUser: data.from?.name ?? "",
|
|
31758
31737
|
sentAt: data.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31759
|
-
recipients,
|
|
31738
|
+
recipients: mapRecipients(data.recipients),
|
|
31760
31739
|
body: data.body ?? args.body,
|
|
31761
31740
|
fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31762
31741
|
replyToId: resolvedReplyTo,
|
|
@@ -31778,16 +31757,13 @@ function registerMessageTools(server2, client2) {
|
|
|
31778
31757
|
}
|
|
31779
31758
|
}
|
|
31780
31759
|
if (args.draftId !== void 0) {
|
|
31781
|
-
|
|
31782
|
-
form.append("messageIds", String(args.draftId));
|
|
31783
|
-
await client2.request("DELETE", "/pub/v1/messages", form);
|
|
31760
|
+
await deleteOFWMessages(client2, [args.draftId]);
|
|
31784
31761
|
deleteDraft(args.draftId);
|
|
31785
31762
|
}
|
|
31786
31763
|
const text = data ? JSON.stringify(data, null, 2) : "Message sent successfully.";
|
|
31787
|
-
|
|
31764
|
+
return textResponse(rewriteNote ? `${rewriteNote}
|
|
31788
31765
|
|
|
31789
|
-
${text}` : text;
|
|
31790
|
-
return { content: [{ type: "text", text: finalText }] };
|
|
31766
|
+
${text}` : text);
|
|
31791
31767
|
});
|
|
31792
31768
|
server2.registerTool("ofw_list_drafts", {
|
|
31793
31769
|
description: "List draft messages from the local OurFamilyWizard cache. Call ofw_sync_messages first if the cache is empty.",
|
|
@@ -31801,7 +31777,7 @@ ${text}` : text;
|
|
|
31801
31777
|
const size = args.size ?? 50;
|
|
31802
31778
|
const drafts = listDrafts({ page, size });
|
|
31803
31779
|
const payload = drafts.length === 0 ? { drafts: [], note: "Cache empty. Call ofw_sync_messages to populate." } : { drafts };
|
|
31804
|
-
return
|
|
31780
|
+
return jsonResponse(payload);
|
|
31805
31781
|
});
|
|
31806
31782
|
server2.registerTool("ofw_save_draft", {
|
|
31807
31783
|
description: "Save a message as a draft in OurFamilyWizard. Recipients are optional. To update an existing draft, provide its messageId. If replyToId is provided, the cache may rewrite it to the latest reply in the thread (note included in response). Attach files by passing their fileIds (from ofw_upload_attachment) in myFileIDs.",
|
|
@@ -31841,11 +31817,7 @@ ${text}` : text;
|
|
|
31841
31817
|
id: data.id,
|
|
31842
31818
|
subject: data.subject ?? args.subject,
|
|
31843
31819
|
body: data.body ?? args.body,
|
|
31844
|
-
recipients: (data.recipients
|
|
31845
|
-
userId: r.user.id,
|
|
31846
|
-
name: r.user.name,
|
|
31847
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31848
|
-
})),
|
|
31820
|
+
recipients: mapRecipients(data.recipients),
|
|
31849
31821
|
replyToId: data.replyToId ?? resolvedReplyTo,
|
|
31850
31822
|
modifiedAt: data.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31851
31823
|
listData: data
|
|
@@ -31853,10 +31825,9 @@ ${text}` : text;
|
|
|
31853
31825
|
upsertDraft(draft);
|
|
31854
31826
|
}
|
|
31855
31827
|
const text = data ? JSON.stringify(data, null, 2) : "Draft saved.";
|
|
31856
|
-
|
|
31828
|
+
return textResponse(rewriteNote ? `${rewriteNote}
|
|
31857
31829
|
|
|
31858
|
-
${text}` : text;
|
|
31859
|
-
return { content: [{ type: "text", text: finalText }] };
|
|
31830
|
+
${text}` : text);
|
|
31860
31831
|
});
|
|
31861
31832
|
server2.registerTool("ofw_delete_draft", {
|
|
31862
31833
|
description: "Delete a draft message from OurFamilyWizard. Also removes the draft from the local cache.",
|
|
@@ -31865,11 +31836,9 @@ ${text}` : text;
|
|
|
31865
31836
|
messageId: external_exports.number().describe("Draft message ID to delete")
|
|
31866
31837
|
}
|
|
31867
31838
|
}, async (args) => {
|
|
31868
|
-
const
|
|
31869
|
-
form.append("messageIds", String(args.messageId));
|
|
31870
|
-
const data = await client2.request("DELETE", "/pub/v1/messages", form);
|
|
31839
|
+
const data = await deleteOFWMessages(client2, [args.messageId]);
|
|
31871
31840
|
deleteDraft(args.messageId);
|
|
31872
|
-
return
|
|
31841
|
+
return data ? jsonResponse(data) : textResponse("Draft deleted.");
|
|
31873
31842
|
});
|
|
31874
31843
|
server2.registerTool("ofw_get_unread_sent", {
|
|
31875
31844
|
description: "List sent messages that have not been read by one or more recipients. Reads from local cache; call ofw_sync_messages first if cache is stale.",
|
|
@@ -31883,9 +31852,7 @@ ${text}` : text;
|
|
|
31883
31852
|
const size = args.size ?? 50;
|
|
31884
31853
|
const sent = listMessages({ folder: "sent", page, size });
|
|
31885
31854
|
if (sent.length === 0) {
|
|
31886
|
-
return {
|
|
31887
|
-
note: "Sent cache is empty. Call ofw_sync_messages to populate."
|
|
31888
|
-
}, null, 2) }] };
|
|
31855
|
+
return jsonResponse({ note: "Sent cache is empty. Call ofw_sync_messages to populate." });
|
|
31889
31856
|
}
|
|
31890
31857
|
const unread = [];
|
|
31891
31858
|
for (const msg of sent) {
|
|
@@ -31895,11 +31862,9 @@ ${text}` : text;
|
|
|
31895
31862
|
}
|
|
31896
31863
|
}
|
|
31897
31864
|
if (unread.length === 0) {
|
|
31898
|
-
return {
|
|
31899
|
-
message: "All scanned sent messages have been read."
|
|
31900
|
-
}, null, 2) }] };
|
|
31865
|
+
return jsonResponse({ message: "All scanned sent messages have been read." });
|
|
31901
31866
|
}
|
|
31902
|
-
return
|
|
31867
|
+
return jsonResponse(unread);
|
|
31903
31868
|
});
|
|
31904
31869
|
server2.registerTool("ofw_upload_attachment", {
|
|
31905
31870
|
description: `Upload a local file to OurFamilyWizard's "My Files" so it can be attached to a message. Returns the fileId \u2014 pass that to ofw_send_message or ofw_save_draft in myFileIDs to attach it. The file is uploaded as PRIVATE (visible only to you) by default; pass shareClass:"SHARED" to share with co-parents directly via the My Files area.`,
|
|
@@ -31911,8 +31876,7 @@ ${text}` : text;
|
|
|
31911
31876
|
description: external_exports.string().describe("Description shown in OFW My Files (default: filename)").optional()
|
|
31912
31877
|
}
|
|
31913
31878
|
}, async (args) => {
|
|
31914
|
-
const
|
|
31915
|
-
const abs = isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
31879
|
+
const abs = expandPath(args.path);
|
|
31916
31880
|
const stat = statSync(abs);
|
|
31917
31881
|
if (!stat.isFile()) throw new Error(`Not a file: ${abs}`);
|
|
31918
31882
|
const buf = readFileSync(abs);
|
|
@@ -31935,71 +31899,95 @@ ${text}` : text;
|
|
|
31935
31899
|
metadata: meta3,
|
|
31936
31900
|
messageId: 0
|
|
31937
31901
|
});
|
|
31938
|
-
return
|
|
31902
|
+
return jsonResponse({
|
|
31939
31903
|
fileId: meta3.fileId,
|
|
31940
31904
|
fileName: meta3.fileName ?? fileName,
|
|
31941
31905
|
mimeType: meta3.fileType ?? mime,
|
|
31942
31906
|
sizeBytes: meta3.sizeInBytes ?? buf.length,
|
|
31943
31907
|
shareClass: meta3.shareClass ?? args.shareClass ?? "PRIVATE",
|
|
31944
31908
|
note: "Pass this fileId to ofw_send_message or ofw_save_draft in myFileIDs to attach it."
|
|
31945
|
-
}
|
|
31909
|
+
});
|
|
31946
31910
|
});
|
|
31947
31911
|
server2.registerTool("ofw_download_attachment", {
|
|
31948
|
-
description:
|
|
31912
|
+
description: 'Download an OFW message attachment by fileId. By default, bytes are saved to disk (~/Downloads/ofw-mcp/) and the response carries the absolute path, mime type, and size for the caller to read back. Pass inline:true to skip disk entirely and return the bytes as MCP content blocks \u2014 images come back as ImageContent (the model sees them directly); other files come back as an EmbeddedResource blob. Use inline for small files where you want the model to read content immediately and the host is sandboxed; use disk for large files or when you want a persistent local copy. The default for `inline` can be flipped server-side via the OFW_INLINE_ATTACHMENTS env var (set to "true" to make inline the default). fileId comes from attachments[].fileId on ofw_get_message. Override disk destination with OFW_ATTACHMENTS_DIR or saveTo. Re-downloading to the same path is a no-op (disk mode only).',
|
|
31949
31913
|
annotations: { readOnlyHint: false },
|
|
31950
31914
|
inputSchema: {
|
|
31951
31915
|
fileId: external_exports.number().describe("Attachment file id (from ofw_get_message \u2192 attachments[].fileId)"),
|
|
31952
|
-
|
|
31953
|
-
|
|
31916
|
+
inline: external_exports.boolean().describe("If true, return bytes inline as MCP content (image for image/*, embedded resource blob otherwise) and skip the disk write. If false, write to disk and return the path. If omitted, falls back to the OFW_INLINE_ATTACHMENTS env var (default: false = disk).").optional(),
|
|
31917
|
+
saveTo: external_exports.string().describe("Absolute path or directory to write to. If a directory, the OFW filename is used. Default: ~/Downloads/ofw-mcp/<fileId>-<filename>. Ignored when inline:true.").optional(),
|
|
31918
|
+
force: external_exports.boolean().describe("Re-download even if already on disk. Default false. Ignored when inline:true (inline always fetches fresh bytes, or reuses an on-disk copy if present).").optional()
|
|
31954
31919
|
}
|
|
31955
31920
|
}, async (args) => {
|
|
31956
31921
|
const fileId = args.fileId;
|
|
31922
|
+
const inline = args.inline ?? getDefaultInlineAttachments();
|
|
31957
31923
|
let cached2 = getAttachment(fileId);
|
|
31958
31924
|
if (!cached2) {
|
|
31959
|
-
|
|
31960
|
-
upsertAttachmentForMessage({
|
|
31961
|
-
fileId: meta3.fileId ?? fileId,
|
|
31962
|
-
fileName: meta3.fileName ?? `file-${fileId}`,
|
|
31963
|
-
label: meta3.label ?? meta3.fileName ?? `file-${fileId}`,
|
|
31964
|
-
mimeType: meta3.fileType ?? "application/octet-stream",
|
|
31965
|
-
sizeBytes: typeof meta3.fileSize === "number" ? meta3.fileSize : null,
|
|
31966
|
-
metadata: meta3,
|
|
31967
|
-
messageId: 0
|
|
31968
|
-
// placeholder; will be cleaned up if a real message references it
|
|
31969
|
-
});
|
|
31925
|
+
await fetchAttachmentMeta(client2, fileId, 0);
|
|
31970
31926
|
cached2 = getAttachment(fileId);
|
|
31971
31927
|
if (!cached2) throw new Error(`failed to fetch metadata for fileId ${fileId}`);
|
|
31972
31928
|
}
|
|
31929
|
+
if (inline) {
|
|
31930
|
+
let bytes = null;
|
|
31931
|
+
let mimeType = cached2.mimeType;
|
|
31932
|
+
let fileName = cached2.fileName;
|
|
31933
|
+
if (cached2.downloadedPath) {
|
|
31934
|
+
try {
|
|
31935
|
+
bytes = readFileSync(cached2.downloadedPath);
|
|
31936
|
+
} catch {
|
|
31937
|
+
}
|
|
31938
|
+
}
|
|
31939
|
+
if (bytes === null) {
|
|
31940
|
+
const response2 = await client2.requestBinary("GET", `/pub/v1/myfiles/${fileId}/data`);
|
|
31941
|
+
bytes = response2.body;
|
|
31942
|
+
mimeType = response2.contentType ?? cached2.mimeType;
|
|
31943
|
+
fileName = response2.suggestedFileName ?? cached2.fileName;
|
|
31944
|
+
}
|
|
31945
|
+
const base643 = bytes.toString("base64");
|
|
31946
|
+
const metaBlock = { type: "text", text: JSON.stringify({
|
|
31947
|
+
fileId,
|
|
31948
|
+
fileName,
|
|
31949
|
+
mimeType,
|
|
31950
|
+
sizeBytes: bytes.length,
|
|
31951
|
+
mode: "inline"
|
|
31952
|
+
}, null, 2) };
|
|
31953
|
+
if (mimeType.startsWith("image/")) {
|
|
31954
|
+
return { content: [metaBlock, { type: "image", data: base643, mimeType }] };
|
|
31955
|
+
}
|
|
31956
|
+
return { content: [metaBlock, { type: "resource", resource: {
|
|
31957
|
+
uri: `ofw://attachment/${fileId}/${encodeURIComponent(fileName)}`,
|
|
31958
|
+
mimeType,
|
|
31959
|
+
blob: base643
|
|
31960
|
+
} }] };
|
|
31961
|
+
}
|
|
31973
31962
|
let dest;
|
|
31974
31963
|
if (args.saveTo) {
|
|
31975
|
-
const
|
|
31976
|
-
const abs =
|
|
31977
|
-
|
|
31978
|
-
dest = isDirArg ? join3(abs, `${fileId}-${cached2.fileName}`) : abs;
|
|
31964
|
+
const isDirArg = args.saveTo.endsWith("/") || args.saveTo.endsWith("\\");
|
|
31965
|
+
const abs = expandPath(args.saveTo);
|
|
31966
|
+
dest = isDirArg ? join4(abs, `${fileId}-${cached2.fileName}`) : abs;
|
|
31979
31967
|
} else {
|
|
31980
|
-
dest =
|
|
31968
|
+
dest = join4(getAttachmentsDir(), `${fileId}-${cached2.fileName}`);
|
|
31981
31969
|
}
|
|
31982
31970
|
if (!args.force && cached2.downloadedPath === dest) {
|
|
31983
|
-
return
|
|
31971
|
+
return jsonResponse({
|
|
31984
31972
|
fileId,
|
|
31985
31973
|
path: dest,
|
|
31986
31974
|
mimeType: cached2.mimeType,
|
|
31987
31975
|
sizeBytes: cached2.sizeBytes,
|
|
31988
31976
|
fileName: cached2.fileName,
|
|
31989
31977
|
note: "already downloaded"
|
|
31990
|
-
}
|
|
31978
|
+
});
|
|
31991
31979
|
}
|
|
31992
31980
|
const response = await client2.requestBinary("GET", `/pub/v1/myfiles/${fileId}/data`);
|
|
31993
31981
|
mkdirSync2(dirname3(dest), { recursive: true });
|
|
31994
31982
|
writeFileSync(dest, response.body);
|
|
31995
31983
|
markAttachmentDownloaded(fileId, dest);
|
|
31996
|
-
return
|
|
31984
|
+
return jsonResponse({
|
|
31997
31985
|
fileId,
|
|
31998
31986
|
path: dest,
|
|
31999
31987
|
mimeType: response.contentType ?? cached2.mimeType,
|
|
32000
31988
|
sizeBytes: response.body.length,
|
|
32001
31989
|
fileName: response.suggestedFileName ?? cached2.fileName
|
|
32002
|
-
}
|
|
31990
|
+
});
|
|
32003
31991
|
});
|
|
32004
31992
|
server2.registerTool("ofw_sync_messages", {
|
|
32005
31993
|
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).",
|
|
@@ -32015,9 +32003,14 @@ ${text}` : text;
|
|
|
32015
32003
|
fetchUnreadBodies: args.fetchUnreadBodies,
|
|
32016
32004
|
deep: args.deep
|
|
32017
32005
|
});
|
|
32018
|
-
return
|
|
32006
|
+
return jsonResponse(result);
|
|
32019
32007
|
});
|
|
32020
32008
|
}
|
|
32009
|
+
async function deleteOFWMessages(client2, ids) {
|
|
32010
|
+
const form = new FormData();
|
|
32011
|
+
for (const id of ids) form.append("messageIds", String(id));
|
|
32012
|
+
return client2.request("DELETE", "/pub/v1/messages", form);
|
|
32013
|
+
}
|
|
32021
32014
|
|
|
32022
32015
|
// src/tools/calendar.ts
|
|
32023
32016
|
function registerCalendarTools(server2, client2) {
|
|
@@ -32035,7 +32028,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32035
32028
|
"GET",
|
|
32036
32029
|
`/pub/v1/calendar/${variant}?startDate=${encodeURIComponent(args.startDate)}&endDate=${encodeURIComponent(args.endDate)}`
|
|
32037
32030
|
);
|
|
32038
|
-
return
|
|
32031
|
+
return jsonResponse(data);
|
|
32039
32032
|
});
|
|
32040
32033
|
server2.registerTool("ofw_create_event", {
|
|
32041
32034
|
description: "Create a calendar event in OurFamilyWizard",
|
|
@@ -32055,7 +32048,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32055
32048
|
}
|
|
32056
32049
|
}, async (args) => {
|
|
32057
32050
|
const data = await client2.request("POST", "/pub/v1/calendar/events", args);
|
|
32058
|
-
return
|
|
32051
|
+
return jsonResponse(data);
|
|
32059
32052
|
});
|
|
32060
32053
|
server2.registerTool("ofw_update_event", {
|
|
32061
32054
|
description: "Update an existing OurFamilyWizard calendar event",
|
|
@@ -32073,7 +32066,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32073
32066
|
}, async (args) => {
|
|
32074
32067
|
const { eventId, ...updateData } = args;
|
|
32075
32068
|
const data = await client2.request("PUT", `/pub/v1/calendar/events/${encodeURIComponent(eventId)}`, updateData);
|
|
32076
|
-
return
|
|
32069
|
+
return jsonResponse(data);
|
|
32077
32070
|
});
|
|
32078
32071
|
server2.registerTool("ofw_delete_event", {
|
|
32079
32072
|
description: "Delete an OurFamilyWizard calendar event",
|
|
@@ -32083,7 +32076,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32083
32076
|
}
|
|
32084
32077
|
}, async (args) => {
|
|
32085
32078
|
await client2.request("DELETE", `/pub/v1/calendar/events/${encodeURIComponent(args.eventId)}`);
|
|
32086
|
-
return
|
|
32079
|
+
return textResponse(`Event ${args.eventId} deleted`);
|
|
32087
32080
|
});
|
|
32088
32081
|
}
|
|
32089
32082
|
|
|
@@ -32094,7 +32087,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32094
32087
|
annotations: { readOnlyHint: true }
|
|
32095
32088
|
}, async () => {
|
|
32096
32089
|
const data = await client2.request("GET", "/pub/v2/expense/expenses/totals");
|
|
32097
|
-
return
|
|
32090
|
+
return jsonResponse(data);
|
|
32098
32091
|
});
|
|
32099
32092
|
server2.registerTool("ofw_list_expenses", {
|
|
32100
32093
|
description: "List OurFamilyWizard expenses with pagination",
|
|
@@ -32107,7 +32100,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32107
32100
|
const start = args.start ?? 0;
|
|
32108
32101
|
const max = args.max ?? 20;
|
|
32109
32102
|
const data = await client2.request("GET", `/pub/v2/expense/expenses?start=${start}&max=${max}`);
|
|
32110
|
-
return
|
|
32103
|
+
return jsonResponse(data);
|
|
32111
32104
|
});
|
|
32112
32105
|
server2.registerTool("ofw_create_expense", {
|
|
32113
32106
|
description: "Log a new expense in OurFamilyWizard",
|
|
@@ -32118,7 +32111,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32118
32111
|
}
|
|
32119
32112
|
}, async (args) => {
|
|
32120
32113
|
const data = await client2.request("POST", "/pub/v2/expense/expenses", args);
|
|
32121
|
-
return
|
|
32114
|
+
return jsonResponse(data);
|
|
32122
32115
|
});
|
|
32123
32116
|
}
|
|
32124
32117
|
|
|
@@ -32135,7 +32128,7 @@ function registerJournalTools(server2, client2) {
|
|
|
32135
32128
|
const start = args.start ?? 1;
|
|
32136
32129
|
const max = args.max ?? 10;
|
|
32137
32130
|
const data = await client2.request("GET", `/pub/v1/journals?start=${start}&max=${max}`);
|
|
32138
|
-
return
|
|
32131
|
+
return jsonResponse(data);
|
|
32139
32132
|
});
|
|
32140
32133
|
server2.registerTool("ofw_create_journal_entry", {
|
|
32141
32134
|
description: "Create a new journal entry in OurFamilyWizard",
|
|
@@ -32146,7 +32139,7 @@ function registerJournalTools(server2, client2) {
|
|
|
32146
32139
|
}
|
|
32147
32140
|
}, async (args) => {
|
|
32148
32141
|
const data = await client2.request("POST", "/pub/v1/journals", args);
|
|
32149
|
-
return
|
|
32142
|
+
return jsonResponse(data);
|
|
32150
32143
|
});
|
|
32151
32144
|
}
|
|
32152
32145
|
|
|
@@ -32161,7 +32154,7 @@ process.emit = function(event, ...args) {
|
|
|
32161
32154
|
}
|
|
32162
32155
|
return originalEmit(event, ...args);
|
|
32163
32156
|
};
|
|
32164
|
-
var server = new McpServer({ name: "ofw", version: "2.0.
|
|
32157
|
+
var server = new McpServer({ name: "ofw", version: "2.0.12" });
|
|
32165
32158
|
registerUserTools(server, client);
|
|
32166
32159
|
registerMessageTools(server, client);
|
|
32167
32160
|
registerCalendarTools(server, client);
|