ofw-mcp 2.0.11 → 2.0.13
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 +184 -210
- package/dist/cache.js +14 -26
- 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 +98 -101
- 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
|
}
|
|
@@ -31420,24 +31406,24 @@ function markAttachmentDownloaded(fileId, path) {
|
|
|
31420
31406
|
}
|
|
31421
31407
|
|
|
31422
31408
|
// src/sync.ts
|
|
31423
|
-
async function
|
|
31424
|
-
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
|
|
31428
|
-
|
|
31429
|
-
|
|
31430
|
-
|
|
31431
|
-
|
|
31432
|
-
|
|
31433
|
-
|
|
31434
|
-
});
|
|
31435
|
-
} catch {
|
|
31436
|
-
}
|
|
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
|
+
});
|
|
31437
31420
|
}
|
|
31438
31421
|
async function fetchAttachmentMetaForMessage(client2, messageId, fileIds) {
|
|
31439
31422
|
for (const fid of fileIds) {
|
|
31440
|
-
|
|
31423
|
+
try {
|
|
31424
|
+
await fetchAttachmentMeta(client2, fid, messageId);
|
|
31425
|
+
} catch {
|
|
31426
|
+
}
|
|
31441
31427
|
}
|
|
31442
31428
|
}
|
|
31443
31429
|
async function resolveFolderIds(client2) {
|
|
@@ -31459,13 +31445,6 @@ async function resolveFolderIds(client2) {
|
|
|
31459
31445
|
setMeta("drafts_folder_id", ids.drafts);
|
|
31460
31446
|
return ids;
|
|
31461
31447
|
}
|
|
31462
|
-
function recipientsFromList(item) {
|
|
31463
|
-
return (item.recipients ?? []).map((r) => ({
|
|
31464
|
-
userId: r.user.id,
|
|
31465
|
-
name: r.user.name,
|
|
31466
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31467
|
-
}));
|
|
31468
|
-
}
|
|
31469
31448
|
async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
31470
31449
|
let page = 1;
|
|
31471
31450
|
let synced = 0;
|
|
@@ -31508,7 +31487,7 @@ async function syncMessageFolder(client2, folder, folderId, opts) {
|
|
|
31508
31487
|
subject: item.subject ?? "(no subject)",
|
|
31509
31488
|
fromUser: item.from?.name ?? "",
|
|
31510
31489
|
sentAt: item.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31511
|
-
recipients:
|
|
31490
|
+
recipients: mapRecipients(item.recipients),
|
|
31512
31491
|
body,
|
|
31513
31492
|
fetchedBodyAt,
|
|
31514
31493
|
replyToId: null,
|
|
@@ -31548,11 +31527,7 @@ async function syncDrafts(client2, draftsFolderId) {
|
|
|
31548
31527
|
id: item.id,
|
|
31549
31528
|
subject: detail.subject ?? item.subject ?? "(no subject)",
|
|
31550
31529
|
body: detail.body ?? "",
|
|
31551
|
-
recipients: (item.recipients
|
|
31552
|
-
userId: r.user?.id ?? 0,
|
|
31553
|
-
name: r.user?.name ?? "",
|
|
31554
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31555
|
-
})),
|
|
31530
|
+
recipients: mapRecipients(item.recipients),
|
|
31556
31531
|
replyToId: item.replyToId ?? null,
|
|
31557
31532
|
modifiedAt,
|
|
31558
31533
|
listData: item
|
|
@@ -31595,7 +31570,7 @@ async function syncAll(client2, opts) {
|
|
|
31595
31570
|
|
|
31596
31571
|
// src/tools/messages.ts
|
|
31597
31572
|
import { mkdirSync as mkdirSync2, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
31598
|
-
import { basename, dirname as dirname3, extname, join as
|
|
31573
|
+
import { basename, dirname as dirname3, extname, join as join4 } from "node:path";
|
|
31599
31574
|
var MIME_BY_EXT = {
|
|
31600
31575
|
".pdf": "application/pdf",
|
|
31601
31576
|
".png": "image/png",
|
|
@@ -31636,7 +31611,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31636
31611
|
annotations: { readOnlyHint: true }
|
|
31637
31612
|
}, async () => {
|
|
31638
31613
|
const data = await client2.request("GET", "/pub/v1/messageFolders?includeFolderCounts=true");
|
|
31639
|
-
return
|
|
31614
|
+
return jsonResponse(data);
|
|
31640
31615
|
});
|
|
31641
31616
|
server2.registerTool("ofw_list_messages", {
|
|
31642
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.",
|
|
@@ -31658,15 +31633,10 @@ function registerMessageTools(server2, client2) {
|
|
|
31658
31633
|
else if (folderArg === "sent") folder = "sent";
|
|
31659
31634
|
else if (folderArg === "both") folder = void 0;
|
|
31660
31635
|
else {
|
|
31661
|
-
return {
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
messages: [],
|
|
31666
|
-
note: 'folderId must be "inbox", "sent", or "both". Numeric OFW folder IDs are not supported by the cache.'
|
|
31667
|
-
}, null, 2)
|
|
31668
|
-
}]
|
|
31669
|
-
};
|
|
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
|
+
});
|
|
31670
31640
|
}
|
|
31671
31641
|
const filter = { folder, since: args.since, until: args.until, q: args.q };
|
|
31672
31642
|
const total = countMessages(filter);
|
|
@@ -31677,7 +31647,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31677
31647
|
} else if (page * size < total) {
|
|
31678
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.`;
|
|
31679
31649
|
}
|
|
31680
|
-
return
|
|
31650
|
+
return jsonResponse(payload);
|
|
31681
31651
|
});
|
|
31682
31652
|
server2.registerTool("ofw_get_message", {
|
|
31683
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).",
|
|
@@ -31700,14 +31670,9 @@ function registerMessageTools(server2, client2) {
|
|
|
31700
31670
|
} catch {
|
|
31701
31671
|
}
|
|
31702
31672
|
}
|
|
31703
|
-
return
|
|
31673
|
+
return jsonResponse({ ...cached2, attachments: attachments2 });
|
|
31704
31674
|
}
|
|
31705
31675
|
const detail = await client2.request("GET", `/pub/v3/messages/${encodeURIComponent(args.messageId)}`);
|
|
31706
|
-
const recipients = (detail.recipients ?? []).map((r) => ({
|
|
31707
|
-
userId: r.user.id,
|
|
31708
|
-
name: r.user.name,
|
|
31709
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31710
|
-
}));
|
|
31711
31676
|
const folder = cached2?.folder ?? "inbox";
|
|
31712
31677
|
const row = {
|
|
31713
31678
|
id: detail.id,
|
|
@@ -31715,7 +31680,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31715
31680
|
subject: detail.subject,
|
|
31716
31681
|
fromUser: detail.from?.name ?? "",
|
|
31717
31682
|
sentAt: detail.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31718
|
-
recipients,
|
|
31683
|
+
recipients: mapRecipients(detail.recipients),
|
|
31719
31684
|
body: detail.body ?? "",
|
|
31720
31685
|
fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31721
31686
|
replyToId: cached2?.replyToId ?? null,
|
|
@@ -31727,7 +31692,7 @@ function registerMessageTools(server2, client2) {
|
|
|
31727
31692
|
await fetchAttachmentMetaForMessage(client2, detail.id, detail.files);
|
|
31728
31693
|
}
|
|
31729
31694
|
const attachments = listAttachmentsForMessage(detail.id);
|
|
31730
|
-
return
|
|
31695
|
+
return jsonResponse({ ...row, attachments });
|
|
31731
31696
|
});
|
|
31732
31697
|
server2.registerTool("ofw_send_message", {
|
|
31733
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.",
|
|
@@ -31764,18 +31729,13 @@ function registerMessageTools(server2, client2) {
|
|
|
31764
31729
|
replyToId: resolvedReplyTo
|
|
31765
31730
|
});
|
|
31766
31731
|
if (data && typeof data.id === "number") {
|
|
31767
|
-
const recipients = (data.recipients ?? []).map((r) => ({
|
|
31768
|
-
userId: r.user.id,
|
|
31769
|
-
name: r.user.name,
|
|
31770
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31771
|
-
}));
|
|
31772
31732
|
const row = {
|
|
31773
31733
|
id: data.id,
|
|
31774
31734
|
folder: "sent",
|
|
31775
31735
|
subject: data.subject ?? args.subject,
|
|
31776
31736
|
fromUser: data.from?.name ?? "",
|
|
31777
31737
|
sentAt: data.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31778
|
-
recipients,
|
|
31738
|
+
recipients: mapRecipients(data.recipients),
|
|
31779
31739
|
body: data.body ?? args.body,
|
|
31780
31740
|
fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31781
31741
|
replyToId: resolvedReplyTo,
|
|
@@ -31797,16 +31757,13 @@ function registerMessageTools(server2, client2) {
|
|
|
31797
31757
|
}
|
|
31798
31758
|
}
|
|
31799
31759
|
if (args.draftId !== void 0) {
|
|
31800
|
-
|
|
31801
|
-
form.append("messageIds", String(args.draftId));
|
|
31802
|
-
await client2.request("DELETE", "/pub/v1/messages", form);
|
|
31760
|
+
await deleteOFWMessages(client2, [args.draftId]);
|
|
31803
31761
|
deleteDraft(args.draftId);
|
|
31804
31762
|
}
|
|
31805
31763
|
const text = data ? JSON.stringify(data, null, 2) : "Message sent successfully.";
|
|
31806
|
-
|
|
31764
|
+
return textResponse(rewriteNote ? `${rewriteNote}
|
|
31807
31765
|
|
|
31808
|
-
${text}` : text;
|
|
31809
|
-
return { content: [{ type: "text", text: finalText }] };
|
|
31766
|
+
${text}` : text);
|
|
31810
31767
|
});
|
|
31811
31768
|
server2.registerTool("ofw_list_drafts", {
|
|
31812
31769
|
description: "List draft messages from the local OurFamilyWizard cache. Call ofw_sync_messages first if the cache is empty.",
|
|
@@ -31820,7 +31777,7 @@ ${text}` : text;
|
|
|
31820
31777
|
const size = args.size ?? 50;
|
|
31821
31778
|
const drafts = listDrafts({ page, size });
|
|
31822
31779
|
const payload = drafts.length === 0 ? { drafts: [], note: "Cache empty. Call ofw_sync_messages to populate." } : { drafts };
|
|
31823
|
-
return
|
|
31780
|
+
return jsonResponse(payload);
|
|
31824
31781
|
});
|
|
31825
31782
|
server2.registerTool("ofw_save_draft", {
|
|
31826
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.",
|
|
@@ -31860,11 +31817,7 @@ ${text}` : text;
|
|
|
31860
31817
|
id: data.id,
|
|
31861
31818
|
subject: data.subject ?? args.subject,
|
|
31862
31819
|
body: data.body ?? args.body,
|
|
31863
|
-
recipients: (data.recipients
|
|
31864
|
-
userId: r.user.id,
|
|
31865
|
-
name: r.user.name,
|
|
31866
|
-
viewedAt: r.viewed?.dateTime ?? null
|
|
31867
|
-
})),
|
|
31820
|
+
recipients: mapRecipients(data.recipients),
|
|
31868
31821
|
replyToId: data.replyToId ?? resolvedReplyTo,
|
|
31869
31822
|
modifiedAt: data.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31870
31823
|
listData: data
|
|
@@ -31872,10 +31825,9 @@ ${text}` : text;
|
|
|
31872
31825
|
upsertDraft(draft);
|
|
31873
31826
|
}
|
|
31874
31827
|
const text = data ? JSON.stringify(data, null, 2) : "Draft saved.";
|
|
31875
|
-
|
|
31828
|
+
return textResponse(rewriteNote ? `${rewriteNote}
|
|
31876
31829
|
|
|
31877
|
-
${text}` : text;
|
|
31878
|
-
return { content: [{ type: "text", text: finalText }] };
|
|
31830
|
+
${text}` : text);
|
|
31879
31831
|
});
|
|
31880
31832
|
server2.registerTool("ofw_delete_draft", {
|
|
31881
31833
|
description: "Delete a draft message from OurFamilyWizard. Also removes the draft from the local cache.",
|
|
@@ -31884,11 +31836,9 @@ ${text}` : text;
|
|
|
31884
31836
|
messageId: external_exports.number().describe("Draft message ID to delete")
|
|
31885
31837
|
}
|
|
31886
31838
|
}, async (args) => {
|
|
31887
|
-
const
|
|
31888
|
-
form.append("messageIds", String(args.messageId));
|
|
31889
|
-
const data = await client2.request("DELETE", "/pub/v1/messages", form);
|
|
31839
|
+
const data = await deleteOFWMessages(client2, [args.messageId]);
|
|
31890
31840
|
deleteDraft(args.messageId);
|
|
31891
|
-
return
|
|
31841
|
+
return data ? jsonResponse(data) : textResponse("Draft deleted.");
|
|
31892
31842
|
});
|
|
31893
31843
|
server2.registerTool("ofw_get_unread_sent", {
|
|
31894
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.",
|
|
@@ -31902,9 +31852,7 @@ ${text}` : text;
|
|
|
31902
31852
|
const size = args.size ?? 50;
|
|
31903
31853
|
const sent = listMessages({ folder: "sent", page, size });
|
|
31904
31854
|
if (sent.length === 0) {
|
|
31905
|
-
return {
|
|
31906
|
-
note: "Sent cache is empty. Call ofw_sync_messages to populate."
|
|
31907
|
-
}, null, 2) }] };
|
|
31855
|
+
return jsonResponse({ note: "Sent cache is empty. Call ofw_sync_messages to populate." });
|
|
31908
31856
|
}
|
|
31909
31857
|
const unread = [];
|
|
31910
31858
|
for (const msg of sent) {
|
|
@@ -31914,11 +31862,9 @@ ${text}` : text;
|
|
|
31914
31862
|
}
|
|
31915
31863
|
}
|
|
31916
31864
|
if (unread.length === 0) {
|
|
31917
|
-
return {
|
|
31918
|
-
message: "All scanned sent messages have been read."
|
|
31919
|
-
}, null, 2) }] };
|
|
31865
|
+
return jsonResponse({ message: "All scanned sent messages have been read." });
|
|
31920
31866
|
}
|
|
31921
|
-
return
|
|
31867
|
+
return jsonResponse(unread);
|
|
31922
31868
|
});
|
|
31923
31869
|
server2.registerTool("ofw_upload_attachment", {
|
|
31924
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.`,
|
|
@@ -31930,8 +31876,7 @@ ${text}` : text;
|
|
|
31930
31876
|
description: external_exports.string().describe("Description shown in OFW My Files (default: filename)").optional()
|
|
31931
31877
|
}
|
|
31932
31878
|
}, async (args) => {
|
|
31933
|
-
const
|
|
31934
|
-
const abs = isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
31879
|
+
const abs = expandPath(args.path);
|
|
31935
31880
|
const stat = statSync(abs);
|
|
31936
31881
|
if (!stat.isFile()) throw new Error(`Not a file: ${abs}`);
|
|
31937
31882
|
const buf = readFileSync(abs);
|
|
@@ -31954,71 +31899,95 @@ ${text}` : text;
|
|
|
31954
31899
|
metadata: meta3,
|
|
31955
31900
|
messageId: 0
|
|
31956
31901
|
});
|
|
31957
|
-
return
|
|
31902
|
+
return jsonResponse({
|
|
31958
31903
|
fileId: meta3.fileId,
|
|
31959
31904
|
fileName: meta3.fileName ?? fileName,
|
|
31960
31905
|
mimeType: meta3.fileType ?? mime,
|
|
31961
31906
|
sizeBytes: meta3.sizeInBytes ?? buf.length,
|
|
31962
31907
|
shareClass: meta3.shareClass ?? args.shareClass ?? "PRIVATE",
|
|
31963
31908
|
note: "Pass this fileId to ofw_send_message or ofw_save_draft in myFileIDs to attach it."
|
|
31964
|
-
}
|
|
31909
|
+
});
|
|
31965
31910
|
});
|
|
31966
31911
|
server2.registerTool("ofw_download_attachment", {
|
|
31967
|
-
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).',
|
|
31968
31913
|
annotations: { readOnlyHint: false },
|
|
31969
31914
|
inputSchema: {
|
|
31970
31915
|
fileId: external_exports.number().describe("Attachment file id (from ofw_get_message \u2192 attachments[].fileId)"),
|
|
31971
|
-
|
|
31972
|
-
|
|
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()
|
|
31973
31919
|
}
|
|
31974
31920
|
}, async (args) => {
|
|
31975
31921
|
const fileId = args.fileId;
|
|
31922
|
+
const inline = args.inline ?? getDefaultInlineAttachments();
|
|
31976
31923
|
let cached2 = getAttachment(fileId);
|
|
31977
31924
|
if (!cached2) {
|
|
31978
|
-
|
|
31979
|
-
upsertAttachmentForMessage({
|
|
31980
|
-
fileId: meta3.fileId ?? fileId,
|
|
31981
|
-
fileName: meta3.fileName ?? `file-${fileId}`,
|
|
31982
|
-
label: meta3.label ?? meta3.fileName ?? `file-${fileId}`,
|
|
31983
|
-
mimeType: meta3.fileType ?? "application/octet-stream",
|
|
31984
|
-
sizeBytes: typeof meta3.fileSize === "number" ? meta3.fileSize : null,
|
|
31985
|
-
metadata: meta3,
|
|
31986
|
-
messageId: 0
|
|
31987
|
-
// placeholder; will be cleaned up if a real message references it
|
|
31988
|
-
});
|
|
31925
|
+
await fetchAttachmentMeta(client2, fileId, 0);
|
|
31989
31926
|
cached2 = getAttachment(fileId);
|
|
31990
31927
|
if (!cached2) throw new Error(`failed to fetch metadata for fileId ${fileId}`);
|
|
31991
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
|
+
}
|
|
31992
31962
|
let dest;
|
|
31993
31963
|
if (args.saveTo) {
|
|
31994
|
-
const
|
|
31995
|
-
const abs =
|
|
31996
|
-
|
|
31997
|
-
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;
|
|
31998
31967
|
} else {
|
|
31999
|
-
dest =
|
|
31968
|
+
dest = join4(getAttachmentsDir(), `${fileId}-${cached2.fileName}`);
|
|
32000
31969
|
}
|
|
32001
31970
|
if (!args.force && cached2.downloadedPath === dest) {
|
|
32002
|
-
return
|
|
31971
|
+
return jsonResponse({
|
|
32003
31972
|
fileId,
|
|
32004
31973
|
path: dest,
|
|
32005
31974
|
mimeType: cached2.mimeType,
|
|
32006
31975
|
sizeBytes: cached2.sizeBytes,
|
|
32007
31976
|
fileName: cached2.fileName,
|
|
32008
31977
|
note: "already downloaded"
|
|
32009
|
-
}
|
|
31978
|
+
});
|
|
32010
31979
|
}
|
|
32011
31980
|
const response = await client2.requestBinary("GET", `/pub/v1/myfiles/${fileId}/data`);
|
|
32012
31981
|
mkdirSync2(dirname3(dest), { recursive: true });
|
|
32013
31982
|
writeFileSync(dest, response.body);
|
|
32014
31983
|
markAttachmentDownloaded(fileId, dest);
|
|
32015
|
-
return
|
|
31984
|
+
return jsonResponse({
|
|
32016
31985
|
fileId,
|
|
32017
31986
|
path: dest,
|
|
32018
31987
|
mimeType: response.contentType ?? cached2.mimeType,
|
|
32019
31988
|
sizeBytes: response.body.length,
|
|
32020
31989
|
fileName: response.suggestedFileName ?? cached2.fileName
|
|
32021
|
-
}
|
|
31990
|
+
});
|
|
32022
31991
|
});
|
|
32023
31992
|
server2.registerTool("ofw_sync_messages", {
|
|
32024
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).",
|
|
@@ -32034,9 +32003,14 @@ ${text}` : text;
|
|
|
32034
32003
|
fetchUnreadBodies: args.fetchUnreadBodies,
|
|
32035
32004
|
deep: args.deep
|
|
32036
32005
|
});
|
|
32037
|
-
return
|
|
32006
|
+
return jsonResponse(result);
|
|
32038
32007
|
});
|
|
32039
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
|
+
}
|
|
32040
32014
|
|
|
32041
32015
|
// src/tools/calendar.ts
|
|
32042
32016
|
function registerCalendarTools(server2, client2) {
|
|
@@ -32054,7 +32028,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32054
32028
|
"GET",
|
|
32055
32029
|
`/pub/v1/calendar/${variant}?startDate=${encodeURIComponent(args.startDate)}&endDate=${encodeURIComponent(args.endDate)}`
|
|
32056
32030
|
);
|
|
32057
|
-
return
|
|
32031
|
+
return jsonResponse(data);
|
|
32058
32032
|
});
|
|
32059
32033
|
server2.registerTool("ofw_create_event", {
|
|
32060
32034
|
description: "Create a calendar event in OurFamilyWizard",
|
|
@@ -32074,7 +32048,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32074
32048
|
}
|
|
32075
32049
|
}, async (args) => {
|
|
32076
32050
|
const data = await client2.request("POST", "/pub/v1/calendar/events", args);
|
|
32077
|
-
return
|
|
32051
|
+
return jsonResponse(data);
|
|
32078
32052
|
});
|
|
32079
32053
|
server2.registerTool("ofw_update_event", {
|
|
32080
32054
|
description: "Update an existing OurFamilyWizard calendar event",
|
|
@@ -32092,7 +32066,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32092
32066
|
}, async (args) => {
|
|
32093
32067
|
const { eventId, ...updateData } = args;
|
|
32094
32068
|
const data = await client2.request("PUT", `/pub/v1/calendar/events/${encodeURIComponent(eventId)}`, updateData);
|
|
32095
|
-
return
|
|
32069
|
+
return jsonResponse(data);
|
|
32096
32070
|
});
|
|
32097
32071
|
server2.registerTool("ofw_delete_event", {
|
|
32098
32072
|
description: "Delete an OurFamilyWizard calendar event",
|
|
@@ -32102,7 +32076,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
32102
32076
|
}
|
|
32103
32077
|
}, async (args) => {
|
|
32104
32078
|
await client2.request("DELETE", `/pub/v1/calendar/events/${encodeURIComponent(args.eventId)}`);
|
|
32105
|
-
return
|
|
32079
|
+
return textResponse(`Event ${args.eventId} deleted`);
|
|
32106
32080
|
});
|
|
32107
32081
|
}
|
|
32108
32082
|
|
|
@@ -32113,7 +32087,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32113
32087
|
annotations: { readOnlyHint: true }
|
|
32114
32088
|
}, async () => {
|
|
32115
32089
|
const data = await client2.request("GET", "/pub/v2/expense/expenses/totals");
|
|
32116
|
-
return
|
|
32090
|
+
return jsonResponse(data);
|
|
32117
32091
|
});
|
|
32118
32092
|
server2.registerTool("ofw_list_expenses", {
|
|
32119
32093
|
description: "List OurFamilyWizard expenses with pagination",
|
|
@@ -32126,7 +32100,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32126
32100
|
const start = args.start ?? 0;
|
|
32127
32101
|
const max = args.max ?? 20;
|
|
32128
32102
|
const data = await client2.request("GET", `/pub/v2/expense/expenses?start=${start}&max=${max}`);
|
|
32129
|
-
return
|
|
32103
|
+
return jsonResponse(data);
|
|
32130
32104
|
});
|
|
32131
32105
|
server2.registerTool("ofw_create_expense", {
|
|
32132
32106
|
description: "Log a new expense in OurFamilyWizard",
|
|
@@ -32137,7 +32111,7 @@ function registerExpenseTools(server2, client2) {
|
|
|
32137
32111
|
}
|
|
32138
32112
|
}, async (args) => {
|
|
32139
32113
|
const data = await client2.request("POST", "/pub/v2/expense/expenses", args);
|
|
32140
|
-
return
|
|
32114
|
+
return jsonResponse(data);
|
|
32141
32115
|
});
|
|
32142
32116
|
}
|
|
32143
32117
|
|
|
@@ -32154,7 +32128,7 @@ function registerJournalTools(server2, client2) {
|
|
|
32154
32128
|
const start = args.start ?? 1;
|
|
32155
32129
|
const max = args.max ?? 10;
|
|
32156
32130
|
const data = await client2.request("GET", `/pub/v1/journals?start=${start}&max=${max}`);
|
|
32157
|
-
return
|
|
32131
|
+
return jsonResponse(data);
|
|
32158
32132
|
});
|
|
32159
32133
|
server2.registerTool("ofw_create_journal_entry", {
|
|
32160
32134
|
description: "Create a new journal entry in OurFamilyWizard",
|
|
@@ -32165,7 +32139,7 @@ function registerJournalTools(server2, client2) {
|
|
|
32165
32139
|
}
|
|
32166
32140
|
}, async (args) => {
|
|
32167
32141
|
const data = await client2.request("POST", "/pub/v1/journals", args);
|
|
32168
|
-
return
|
|
32142
|
+
return jsonResponse(data);
|
|
32169
32143
|
});
|
|
32170
32144
|
}
|
|
32171
32145
|
|
|
@@ -32180,7 +32154,7 @@ process.emit = function(event, ...args) {
|
|
|
32180
32154
|
}
|
|
32181
32155
|
return originalEmit(event, ...args);
|
|
32182
32156
|
};
|
|
32183
|
-
var server = new McpServer({ name: "ofw", version: "2.0.
|
|
32157
|
+
var server = new McpServer({ name: "ofw", version: "2.0.13" });
|
|
32184
32158
|
registerUserTools(server, client);
|
|
32185
32159
|
registerMessageTools(server, client);
|
|
32186
32160
|
registerCalendarTools(server, client);
|