ofw-mcp 2.0.13 → 2.0.15
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/README.md +20 -9
- package/dist/auth-password.js +57 -0
- package/dist/auth.js +135 -0
- package/dist/bundle.js +11602 -5743
- package/dist/client.js +43 -60
- package/dist/config.js +18 -8
- package/dist/index.js +1 -1
- package/dist/sync.js +9 -4
- package/dist/tools/messages.js +56 -26
- package/package.json +3 -2
- package/server.json +2 -2
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import { resolveAuth } from './auth.js';
|
|
3
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb bundle)
|
|
4
5
|
try {
|
|
5
6
|
const { config } = await import('dotenv');
|
|
@@ -9,24 +10,6 @@ try {
|
|
|
9
10
|
catch {
|
|
10
11
|
// not available — rely on process.env (mcpb sets credentials via mcp_config.env)
|
|
11
12
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Read an env var, trim whitespace, and treat as unset if blank or if the value
|
|
14
|
-
* looks like an unsubstituted shell placeholder (e.g. `${FOO}`) — defends
|
|
15
|
-
* against MCP hosts that pass .mcp.json env blocks through unexpanded.
|
|
16
|
-
*/
|
|
17
|
-
function readVar(key) {
|
|
18
|
-
const raw = process.env[key];
|
|
19
|
-
if (typeof raw !== 'string')
|
|
20
|
-
return undefined;
|
|
21
|
-
const trimmed = raw.trim();
|
|
22
|
-
if (trimmed.length === 0)
|
|
23
|
-
return undefined;
|
|
24
|
-
if (trimmed === 'undefined' || trimmed === 'null')
|
|
25
|
-
return undefined;
|
|
26
|
-
if (/^\$\{[^}]*\}$/.test(trimmed))
|
|
27
|
-
return undefined;
|
|
28
|
-
return trimmed;
|
|
29
|
-
}
|
|
30
13
|
const BASE_URL = 'https://ofw.ourfamilywizard.com';
|
|
31
14
|
const OFW_PROTOCOL_HEADERS = {
|
|
32
15
|
'ofw-client': 'WebApplication',
|
|
@@ -48,6 +31,19 @@ function parseContentDispositionFilename(cd) {
|
|
|
48
31
|
const m = /filename="?([^";]+)"?/i.exec(cd);
|
|
49
32
|
return m ? m[1] : null;
|
|
50
33
|
}
|
|
34
|
+
// Set OFW_DEBUG_LOG=1 (or true/yes/on) to log every OFW request/response to
|
|
35
|
+
// stderr. Authorization is redacted. Bodies are logged in full — set this
|
|
36
|
+
// only when debugging, never in normal use.
|
|
37
|
+
function debugLogEnabled() {
|
|
38
|
+
const v = process.env.OFW_DEBUG_LOG;
|
|
39
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
40
|
+
}
|
|
41
|
+
function redactHeaders(h) {
|
|
42
|
+
const out = { ...h };
|
|
43
|
+
if (out.Authorization)
|
|
44
|
+
out.Authorization = `Bearer ${out.Authorization.slice(7, 17)}…`;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
51
47
|
export class OFWClient {
|
|
52
48
|
token = null;
|
|
53
49
|
tokenExpiry = null;
|
|
@@ -55,6 +51,9 @@ export class OFWClient {
|
|
|
55
51
|
await this.ensureAuthenticated();
|
|
56
52
|
const response = await this.fetchWithRetry(method, path, body, 'application/json', false);
|
|
57
53
|
const text = await response.text();
|
|
54
|
+
if (debugLogEnabled()) {
|
|
55
|
+
console.error(`[ofw-debug] response body: ${text || '<empty>'}`);
|
|
56
|
+
}
|
|
58
57
|
return (text ? JSON.parse(text) : null);
|
|
59
58
|
}
|
|
60
59
|
/** Like `request`, but returns the raw bytes plus Content-Type/-Disposition metadata. */
|
|
@@ -79,11 +78,25 @@ export class OFWClient {
|
|
|
79
78
|
};
|
|
80
79
|
if (body !== undefined && !isFormData)
|
|
81
80
|
headers['Content-Type'] = 'application/json';
|
|
82
|
-
const
|
|
81
|
+
const url = `${BASE_URL}${path}`;
|
|
82
|
+
if (debugLogEnabled()) {
|
|
83
|
+
const bodyPreview = body === undefined
|
|
84
|
+
? '<none>'
|
|
85
|
+
: isFormData
|
|
86
|
+
? `<FormData entries=${Array.from(body.keys()).join(',')}>`
|
|
87
|
+
: JSON.stringify(body);
|
|
88
|
+
console.error(`[ofw-debug] → ${method} ${url}${isRetry ? ' (retry)' : ''}`);
|
|
89
|
+
console.error(`[ofw-debug] headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
90
|
+
console.error(`[ofw-debug] body: ${bodyPreview}`);
|
|
91
|
+
}
|
|
92
|
+
const response = await fetch(url, {
|
|
83
93
|
method,
|
|
84
94
|
headers,
|
|
85
95
|
...(body !== undefined ? { body: isFormData ? body : JSON.stringify(body) } : {}),
|
|
86
96
|
});
|
|
97
|
+
if (debugLogEnabled()) {
|
|
98
|
+
console.error(`[ofw-debug] ← ${response.status} ${response.statusText}`);
|
|
99
|
+
}
|
|
87
100
|
if (response.status === 401 && !isRetry) {
|
|
88
101
|
this.token = null;
|
|
89
102
|
this.tokenExpiry = null;
|
|
@@ -107,48 +120,18 @@ export class OFWClient {
|
|
|
107
120
|
return;
|
|
108
121
|
await this.login();
|
|
109
122
|
}
|
|
123
|
+
// Auth resolution is delegated to `./auth.ts`. This client doesn't care
|
|
124
|
+
// whether the token came from a password POST or from a one-shot
|
|
125
|
+
// fetchproxy session-snapshot — it just consumes the result.
|
|
126
|
+
//
|
|
127
|
+
// If `expiresAt` is missing (the fetchproxy path on a tab whose
|
|
128
|
+
// browser didn't persist tokenExpiry), we fall back to the same 6h
|
|
129
|
+
// estimate the password path uses. The 401-replay path covers us if
|
|
130
|
+
// the estimate is wrong.
|
|
110
131
|
async login() {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw new Error('OFW_USERNAME and OFW_PASSWORD must be set');
|
|
115
|
-
}
|
|
116
|
-
// Spring Security requires a SESSION cookie before accepting the login POST.
|
|
117
|
-
// GET /ofw/login.form with redirect:manual to capture the Set-Cookie from the 303 response.
|
|
118
|
-
const initResponse = await fetch(`${BASE_URL}/ofw/login.form`, {
|
|
119
|
-
headers: { ...OFW_PROTOCOL_HEADERS },
|
|
120
|
-
redirect: 'manual',
|
|
121
|
-
});
|
|
122
|
-
// Extract just the SESSION=value part (strip attributes like Path, Secure, etc.)
|
|
123
|
-
const setCookie = initResponse.headers.get('set-cookie') ?? '';
|
|
124
|
-
const sessionCookie = setCookie.split(';')[0];
|
|
125
|
-
const response = await fetch(`${BASE_URL}/ofw/login`, {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: {
|
|
128
|
-
...OFW_PROTOCOL_HEADERS,
|
|
129
|
-
Accept: 'application/json',
|
|
130
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
131
|
-
...(sessionCookie ? { Cookie: sessionCookie } : {}),
|
|
132
|
-
},
|
|
133
|
-
body: new URLSearchParams({
|
|
134
|
-
submit: 'Sign In',
|
|
135
|
-
_eventId: 'submit',
|
|
136
|
-
username,
|
|
137
|
-
password,
|
|
138
|
-
}).toString(),
|
|
139
|
-
});
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
throw new Error(`OFW login failed: ${response.status} ${response.statusText}`);
|
|
142
|
-
}
|
|
143
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
144
|
-
if (!contentType.includes('application/json')) {
|
|
145
|
-
const body = await response.text();
|
|
146
|
-
throw new Error(`OFW login returned unexpected response (${contentType}): ${body.substring(0, 200)}`);
|
|
147
|
-
}
|
|
148
|
-
const data = (await response.json());
|
|
149
|
-
this.token = data.auth;
|
|
150
|
-
// Token expiry not returned by login endpoint; use 6h as a safe default
|
|
151
|
-
this.tokenExpiry = new Date(Date.now() + 6 * 60 * 60 * 1000);
|
|
132
|
+
const { token, expiresAt } = await resolveAuth();
|
|
133
|
+
this.token = token;
|
|
134
|
+
this.tokenExpiry = expiresAt ?? new Date(Date.now() + 6 * 60 * 60 * 1000);
|
|
152
135
|
}
|
|
153
136
|
isTokenExpiredSoon() {
|
|
154
137
|
if (!this.token || !this.tokenExpiry)
|
package/dist/config.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
// Cache identity drives the per-user SQLite DB filename. Order of preference:
|
|
5
|
+
// 1. OFW_CACHE_IDENTITY — explicit override for users who want to label the
|
|
6
|
+
// cache themselves (e.g. when authing via fetchproxy and OFW_USERNAME is
|
|
7
|
+
// not set).
|
|
8
|
+
// 2. OFW_USERNAME — legacy path; existing users keep their existing DB.
|
|
9
|
+
// 3. "_default" — fallback for fetchproxy-only setups where neither is set.
|
|
10
|
+
// Single-user installs are fine on this; multi-account users should set
|
|
11
|
+
// OFW_CACHE_IDENTITY explicitly so their caches don't collide.
|
|
12
|
+
function readCacheIdentity() {
|
|
13
|
+
const explicit = process.env.OFW_CACHE_IDENTITY;
|
|
14
|
+
if (typeof explicit === 'string' && explicit.trim().length > 0)
|
|
15
|
+
return explicit.trim();
|
|
16
|
+
const username = process.env.OFW_USERNAME;
|
|
17
|
+
if (typeof username === 'string' && username.trim().length > 0)
|
|
18
|
+
return username.trim();
|
|
19
|
+
return '_default';
|
|
10
20
|
}
|
|
11
21
|
export function getCacheDir() {
|
|
12
22
|
const override = process.env.OFW_CACHE_DIR;
|
|
@@ -15,8 +25,8 @@ export function getCacheDir() {
|
|
|
15
25
|
return join(homedir(), '.cache', 'ofw-mcp');
|
|
16
26
|
}
|
|
17
27
|
export function getCacheDbPath() {
|
|
18
|
-
const
|
|
19
|
-
const hash = createHash('sha256').update(
|
|
28
|
+
const identity = readCacheIdentity();
|
|
29
|
+
const hash = createHash('sha256').update(identity).digest('hex').slice(0, 16);
|
|
20
30
|
return join(getCacheDir(), `${hash}.db`);
|
|
21
31
|
}
|
|
22
32
|
export function getAttachmentsDir() {
|
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.15' });
|
|
21
21
|
registerUserTools(server, client);
|
|
22
22
|
registerMessageTools(server, client);
|
|
23
23
|
registerCalendarTools(server, client);
|
package/dist/sync.js
CHANGED
|
@@ -127,10 +127,10 @@ export async function syncDrafts(client, draftsFolderId) {
|
|
|
127
127
|
for (const item of items) {
|
|
128
128
|
seenIds.add(item.id);
|
|
129
129
|
const modifiedAt = item.date?.dateTime ?? new Date().toISOString();
|
|
130
|
+
// OFW's list endpoint's `date.dateTime` is NOT a reliable modification
|
|
131
|
+
// timestamp for drafts — direct UI edits don't bump it — so we can't
|
|
132
|
+
// use it to skip the detail fetch. Always re-fetch; drafts are few.
|
|
130
133
|
const existing = getDraft(item.id);
|
|
131
|
-
if (existing && existing.modifiedAt === modifiedAt) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
134
|
const detail = await client.request('GET', `/pub/v3/messages/${item.id}`);
|
|
135
135
|
const row = {
|
|
136
136
|
id: item.id,
|
|
@@ -142,7 +142,12 @@ export async function syncDrafts(client, draftsFolderId) {
|
|
|
142
142
|
listData: item,
|
|
143
143
|
};
|
|
144
144
|
upsertDraft(row);
|
|
145
|
-
|
|
145
|
+
if (!existing
|
|
146
|
+
|| existing.body !== row.body
|
|
147
|
+
|| existing.subject !== row.subject
|
|
148
|
+
|| existing.replyToId !== row.replyToId) {
|
|
149
|
+
synced++;
|
|
150
|
+
}
|
|
146
151
|
}
|
|
147
152
|
for (const id of listDraftIds()) {
|
|
148
153
|
if (!seenIds.has(id))
|
package/dist/tools/messages.js
CHANGED
|
@@ -149,7 +149,7 @@ export function registerMessageTools(server, client) {
|
|
|
149
149
|
return jsonResponse({ ...row, attachments });
|
|
150
150
|
});
|
|
151
151
|
server.registerTool('ofw_send_message', {
|
|
152
|
-
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.',
|
|
152
|
+
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. After sending, the tool re-fetches the message from OFW to populate the local cache and link attachments to the new message id.',
|
|
153
153
|
annotations: { destructiveHint: true },
|
|
154
154
|
inputSchema: {
|
|
155
155
|
subject: z.string().describe('Message subject'),
|
|
@@ -173,6 +173,9 @@ export function registerMessageTools(server, client) {
|
|
|
173
173
|
chainRootId = parent?.chainRootId ?? parent?.id ?? requestedReplyTo;
|
|
174
174
|
}
|
|
175
175
|
const myFileIDs = args.myFileIDs ?? [];
|
|
176
|
+
// OFW's POST /pub/v3/messages response is minimal — typically just
|
|
177
|
+
// `{entityId: <id>}` — so the cache write needs to fetch detail
|
|
178
|
+
// afterwards (same shape as ofw_save_draft).
|
|
176
179
|
const data = await client.request('POST', '/pub/v3/messages', {
|
|
177
180
|
subject: args.subject,
|
|
178
181
|
body: args.body,
|
|
@@ -182,21 +185,26 @@ export function registerMessageTools(server, client) {
|
|
|
182
185
|
includeOriginal: resolvedReplyTo !== null,
|
|
183
186
|
replyToId: resolvedReplyTo,
|
|
184
187
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const newId = typeof data?.id === 'number' ? data.id
|
|
189
|
+
: typeof data?.entityId === 'number' ? data.entityId
|
|
190
|
+
: null;
|
|
191
|
+
let persisted = null;
|
|
192
|
+
if (newId !== null) {
|
|
193
|
+
const detail = await client.request('GET', `/pub/v3/messages/${newId}`);
|
|
194
|
+
persisted = {
|
|
195
|
+
id: newId,
|
|
188
196
|
folder: 'sent',
|
|
189
|
-
subject:
|
|
190
|
-
fromUser:
|
|
191
|
-
sentAt:
|
|
192
|
-
recipients: mapRecipients(
|
|
193
|
-
body:
|
|
197
|
+
subject: detail.subject ?? args.subject,
|
|
198
|
+
fromUser: detail.from?.name ?? '',
|
|
199
|
+
sentAt: detail.date?.dateTime ?? new Date().toISOString(),
|
|
200
|
+
recipients: mapRecipients(detail.recipients),
|
|
201
|
+
body: detail.body ?? args.body,
|
|
194
202
|
fetchedBodyAt: new Date().toISOString(),
|
|
195
203
|
replyToId: resolvedReplyTo,
|
|
196
204
|
chainRootId,
|
|
197
|
-
listData:
|
|
205
|
+
listData: detail,
|
|
198
206
|
};
|
|
199
|
-
upsertMessage(
|
|
207
|
+
upsertMessage(persisted);
|
|
200
208
|
// Link attached files to the new message in the attachments cache.
|
|
201
209
|
// We may not have full metadata if the upload happened in a prior
|
|
202
210
|
// session — fall back to what we know.
|
|
@@ -209,7 +217,7 @@ export function registerMessageTools(server, client) {
|
|
|
209
217
|
mimeType: existing?.mimeType ?? 'application/octet-stream',
|
|
210
218
|
sizeBytes: existing?.sizeBytes ?? null,
|
|
211
219
|
metadata: existing?.metadata ?? {},
|
|
212
|
-
messageId:
|
|
220
|
+
messageId: newId,
|
|
213
221
|
});
|
|
214
222
|
}
|
|
215
223
|
}
|
|
@@ -217,7 +225,8 @@ export function registerMessageTools(server, client) {
|
|
|
217
225
|
await deleteOFWMessages(client, [args.draftId]);
|
|
218
226
|
deleteDraft(args.draftId);
|
|
219
227
|
}
|
|
220
|
-
const
|
|
228
|
+
const responseObj = persisted ?? data;
|
|
229
|
+
const text = responseObj ? JSON.stringify(responseObj, null, 2) : 'Message sent successfully.';
|
|
221
230
|
return textResponse(rewriteNote ? `${rewriteNote}\n\n${text}` : text);
|
|
222
231
|
});
|
|
223
232
|
server.registerTool('ofw_list_drafts', {
|
|
@@ -237,7 +246,7 @@ export function registerMessageTools(server, client) {
|
|
|
237
246
|
return jsonResponse(payload);
|
|
238
247
|
});
|
|
239
248
|
server.registerTool('ofw_save_draft', {
|
|
240
|
-
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.',
|
|
249
|
+
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. After saving, the tool re-fetches the draft from OFW to populate the local cache and verify what was actually persisted; if OFW silently no-ops an update (a known issue with repeated updates to the same draft), the response includes a WARNING note with a workaround.',
|
|
241
250
|
annotations: { readOnlyHint: false },
|
|
242
251
|
inputSchema: {
|
|
243
252
|
subject: z.string().describe('Message subject'),
|
|
@@ -269,21 +278,42 @@ export function registerMessageTools(server, client) {
|
|
|
269
278
|
};
|
|
270
279
|
if (args.messageId !== undefined)
|
|
271
280
|
payload.messageId = args.messageId;
|
|
281
|
+
// OFW's POST /pub/v3/messages response for drafts is minimal — typically
|
|
282
|
+
// just `{entityId: <id>}` — and worse, it returns the same success shape
|
|
283
|
+
// even when the server silently no-ops on a subsequent update to the
|
|
284
|
+
// same draft. Don't trust the POST response: extract the id from it,
|
|
285
|
+
// then GET the detail endpoint to repopulate the cache from
|
|
286
|
+
// authoritative server state.
|
|
272
287
|
const data = await client.request('POST', '/pub/v3/messages', payload);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
288
|
+
const newId = typeof data?.id === 'number' ? data.id
|
|
289
|
+
: typeof data?.entityId === 'number' ? data.entityId
|
|
290
|
+
: null;
|
|
291
|
+
let persisted = null;
|
|
292
|
+
let noOpWarning = null;
|
|
293
|
+
if (newId !== null) {
|
|
294
|
+
const detail = await client.request('GET', `/pub/v3/messages/${newId}`);
|
|
295
|
+
persisted = {
|
|
296
|
+
id: newId,
|
|
297
|
+
subject: detail.subject ?? args.subject,
|
|
298
|
+
body: detail.body ?? '',
|
|
299
|
+
recipients: mapRecipients(detail.recipients),
|
|
300
|
+
replyToId: detail.replyToId ?? resolvedReplyTo,
|
|
301
|
+
modifiedAt: detail.date?.dateTime ?? new Date().toISOString(),
|
|
302
|
+
listData: detail,
|
|
282
303
|
};
|
|
283
|
-
upsertDraft(
|
|
304
|
+
upsertDraft(persisted);
|
|
305
|
+
// If this was an update (messageId provided) and OFW's reported body
|
|
306
|
+
// doesn't match what we asked it to save, the server silently
|
|
307
|
+
// dropped the change. Warn the caller so the model can take the
|
|
308
|
+
// create-then-delete fallback.
|
|
309
|
+
if (args.messageId !== undefined && persisted.body !== args.body) {
|
|
310
|
+
noOpWarning = 'WARNING: OFW reported success but the draft body it returned does not match the requested update. The OFW POST /pub/v3/messages endpoint can silently no-op on subsequent updates to the same draft. Workaround: delete this draft (ofw_delete_draft) and create a new one (ofw_save_draft without messageId).';
|
|
311
|
+
}
|
|
284
312
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
313
|
+
const responseObj = persisted ?? data;
|
|
314
|
+
const text = responseObj ? JSON.stringify(responseObj, null, 2) : 'Draft saved.';
|
|
315
|
+
const notes = [rewriteNote, noOpWarning].filter((n) => n !== null).join('\n\n');
|
|
316
|
+
return textResponse(notes ? `${notes}\n\n${text}` : text);
|
|
287
317
|
});
|
|
288
318
|
server.registerTool('ofw_delete_draft', {
|
|
289
319
|
description: 'Delete a draft message from OurFamilyWizard. Also removes the draft from the local cache.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
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>",
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && npm run bundle",
|
|
24
|
-
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --outfile=dist/bundle.js",
|
|
24
|
+
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from \"module\"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js",
|
|
25
25
|
"dev": "node --env-file=.env dist/index.js",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@fetchproxy/bootstrap": "^0.4.2",
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
32
|
"dotenv": "^17.4.0",
|
|
32
33
|
"zod": "^4.4.2"
|
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.15",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.0.
|
|
14
|
+
"version": "2.0.15",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|