ofw-mcp 2.4.1 → 2.4.3
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 +2 -37
- package/.mcp.json +2 -5
- package/dist/bundle.js +187 -67
- package/dist/client.js +0 -6
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/dist/env-bootstrap.js +0 -28
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.4.
|
|
9
|
+
"version": "2.4.3"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"displayName": "OurFamilyWizard",
|
|
15
15
|
"source": "./",
|
|
16
16
|
"description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
|
|
17
|
-
"version": "2.4.
|
|
17
|
+
"version": "2.4.3",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Chris Chall"
|
|
20
20
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw",
|
|
3
3
|
"displayName": "OurFamilyWizard",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.3",
|
|
5
5
|
"description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Chall"
|
|
@@ -17,40 +17,5 @@
|
|
|
17
17
|
"family"
|
|
18
18
|
],
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"mcp": "./.mcp.json"
|
|
21
|
-
"userConfig": {
|
|
22
|
-
"ofw_username": {
|
|
23
|
-
"type": "string",
|
|
24
|
-
"title": "OFW Email",
|
|
25
|
-
"description": "Your OurFamilyWizard login email address. Optional — if omitted, the server falls back to the fetchproxy browser extension (requires being signed in to ourfamilywizard.com in your browser).",
|
|
26
|
-
"required": false
|
|
27
|
-
},
|
|
28
|
-
"ofw_password": {
|
|
29
|
-
"type": "string",
|
|
30
|
-
"title": "OFW Password",
|
|
31
|
-
"description": "Your OurFamilyWizard password. Optional — see ofw_username.",
|
|
32
|
-
"required": false,
|
|
33
|
-
"sensitive": true
|
|
34
|
-
},
|
|
35
|
-
"ofw_inline_attachments": {
|
|
36
|
-
"type": "boolean",
|
|
37
|
-
"title": "Return attachments inline",
|
|
38
|
-
"description": "When on, ofw_download_attachment returns bytes inline as MCP content (images render directly; other files come back as embedded resources) instead of writing to disk. Recommended for sandboxed hosts like Claude Desktop where the model cannot read files written to ~/Downloads. Callers can still override per-call via the tool's `inline` argument.",
|
|
39
|
-
"required": false,
|
|
40
|
-
"default": false
|
|
41
|
-
},
|
|
42
|
-
"ofw_attachments_dir": {
|
|
43
|
-
"type": "directory",
|
|
44
|
-
"title": "Attachments download directory",
|
|
45
|
-
"description": "Directory where ofw_download_attachment writes files when not returning inline. Defaults to ~/Downloads/ofw-mcp/. Pick a directory that is readable by your MCP host.",
|
|
46
|
-
"required": false
|
|
47
|
-
},
|
|
48
|
-
"ofw_write_mode": {
|
|
49
|
-
"type": "string",
|
|
50
|
-
"title": "Write mode",
|
|
51
|
-
"description": "Structural write gate. \"none\" = no write tools registered (read/sync/search only); \"drafts\" = draft-level writes only (save/delete drafts, upload attachments — never send or calendar/expense/journal writes); \"all\" = everything (default). Unrecognized values fail closed to \"none\".",
|
|
52
|
-
"required": false,
|
|
53
|
-
"default": "all"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
20
|
+
"mcp": "./.mcp.json"
|
|
56
21
|
}
|
package/.mcp.json
CHANGED
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
"command": "npx",
|
|
5
5
|
"args": ["-y", "ofw-mcp"],
|
|
6
6
|
"env": {
|
|
7
|
-
"OFW_USERNAME": "${
|
|
8
|
-
"OFW_PASSWORD": "${
|
|
9
|
-
"OFW_INLINE_ATTACHMENTS": "${user_config.ofw_inline_attachments}",
|
|
10
|
-
"OFW_ATTACHMENTS_DIR": "${user_config.ofw_attachments_dir}",
|
|
11
|
-
"OFW_WRITE_MODE": "${user_config.ofw_write_mode}"
|
|
7
|
+
"OFW_USERNAME": "${OFW_USERNAME}",
|
|
8
|
+
"OFW_PASSWORD": "${OFW_PASSWORD}"
|
|
12
9
|
}
|
|
13
10
|
}
|
|
14
11
|
}
|
package/dist/bundle.js
CHANGED
|
@@ -35837,10 +35837,20 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
35837
35837
|
|
|
35838
35838
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
35839
35839
|
import { createServer, Server as HttpServer } from "node:http";
|
|
35840
|
+
var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
35840
35841
|
async function electRole(opts) {
|
|
35841
35842
|
const server = createServer();
|
|
35843
|
+
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
35842
35844
|
return new Promise((resolve2, reject) => {
|
|
35845
|
+
let timer;
|
|
35846
|
+
const clearBindTimer = () => {
|
|
35847
|
+
if (timer) {
|
|
35848
|
+
clearTimeout(timer);
|
|
35849
|
+
timer = void 0;
|
|
35850
|
+
}
|
|
35851
|
+
};
|
|
35843
35852
|
const onError = (e) => {
|
|
35853
|
+
clearBindTimer();
|
|
35844
35854
|
server.removeListener("listening", onListening);
|
|
35845
35855
|
if (e.code === "EADDRINUSE") {
|
|
35846
35856
|
try {
|
|
@@ -35853,11 +35863,25 @@ async function electRole(opts) {
|
|
|
35853
35863
|
}
|
|
35854
35864
|
};
|
|
35855
35865
|
const onListening = () => {
|
|
35866
|
+
clearBindTimer();
|
|
35856
35867
|
server.removeListener("error", onError);
|
|
35857
35868
|
resolve2({ role: "host", server });
|
|
35858
35869
|
};
|
|
35859
35870
|
server.once("error", onError);
|
|
35860
35871
|
server.once("listening", onListening);
|
|
35872
|
+
if (bindTimeoutMs > 0) {
|
|
35873
|
+
timer = setTimeout(() => {
|
|
35874
|
+
server.removeListener("error", onError);
|
|
35875
|
+
server.removeListener("listening", onListening);
|
|
35876
|
+
try {
|
|
35877
|
+
server.close();
|
|
35878
|
+
} catch {
|
|
35879
|
+
}
|
|
35880
|
+
reject(new Error(`fetchproxy: bind to ${opts.host}:${opts.port} timed out after ${bindTimeoutMs}ms (port may be in a bad state)`));
|
|
35881
|
+
}, bindTimeoutMs);
|
|
35882
|
+
if (typeof timer.unref === "function")
|
|
35883
|
+
timer.unref();
|
|
35884
|
+
}
|
|
35861
35885
|
server.listen(opts.port, opts.host);
|
|
35862
35886
|
});
|
|
35863
35887
|
}
|
|
@@ -35950,6 +35974,44 @@ var SessionState = class {
|
|
|
35950
35974
|
}
|
|
35951
35975
|
};
|
|
35952
35976
|
|
|
35977
|
+
// node_modules/@fetchproxy/server/dist/session-ready.js
|
|
35978
|
+
var SESSION_READY_TIMEOUT_MS = 3e4;
|
|
35979
|
+
var FetchproxySessionNotReadyError = class extends Error {
|
|
35980
|
+
reason;
|
|
35981
|
+
pairCode;
|
|
35982
|
+
mcpId;
|
|
35983
|
+
hint;
|
|
35984
|
+
constructor(info) {
|
|
35985
|
+
const pairing = info.pairCode !== null && info.pairCode !== "";
|
|
35986
|
+
const hint = pairing ? `Open the Transporter extension popup and approve pair code ${info.pairCode} for "${info.mcpId}", then retry.` : `The extension is connected but hasn't confirmed a session for "${info.mcpId}" \u2014 sign in to the target site in that browser (and approve the requested scope if it changed), then retry.`;
|
|
35987
|
+
super(`fetchproxy: ${pairing ? "pairing not yet approved" : "no confirmed browser session"} for "${info.mcpId}". ${hint}`);
|
|
35988
|
+
this.name = "FetchproxySessionNotReadyError";
|
|
35989
|
+
this.reason = pairing ? "pair-required" : "not-ready";
|
|
35990
|
+
this.pairCode = pairing ? info.pairCode : null;
|
|
35991
|
+
this.mcpId = info.mcpId;
|
|
35992
|
+
this.hint = hint;
|
|
35993
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
35994
|
+
}
|
|
35995
|
+
};
|
|
35996
|
+
async function awaitSessionReady(ready, opts) {
|
|
35997
|
+
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
35998
|
+
if (ms <= 0)
|
|
35999
|
+
return ready;
|
|
36000
|
+
let timer;
|
|
36001
|
+
const timeout = new Promise((_, reject) => {
|
|
36002
|
+
timer = setTimeout(() => {
|
|
36003
|
+
reject(new FetchproxySessionNotReadyError({ mcpId: opts.mcpId, pairCode: opts.pendingPairCode() }));
|
|
36004
|
+
}, ms);
|
|
36005
|
+
timer.unref?.();
|
|
36006
|
+
});
|
|
36007
|
+
try {
|
|
36008
|
+
return await Promise.race([ready, timeout]);
|
|
36009
|
+
} finally {
|
|
36010
|
+
if (timer)
|
|
36011
|
+
clearTimeout(timer);
|
|
36012
|
+
}
|
|
36013
|
+
}
|
|
36014
|
+
|
|
35953
36015
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
35954
36016
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
35955
36017
|
var enc2 = new TextEncoder();
|
|
@@ -36037,6 +36099,29 @@ async function startHost(opts) {
|
|
|
36037
36099
|
return;
|
|
36038
36100
|
}
|
|
36039
36101
|
if (frame.type === "hello" && frame.role === "server") {
|
|
36102
|
+
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
36103
|
+
const peerSigMsg = concatBytes(enc2.encode(frame.mcpId), fromB64(frame.sessionNonce));
|
|
36104
|
+
const peerSig = fromB64(frame.sessionSig);
|
|
36105
|
+
let peerSigOk = false;
|
|
36106
|
+
try {
|
|
36107
|
+
peerSigOk = await ed25519Verify(peerEdPub, peerSigMsg, peerSig);
|
|
36108
|
+
} catch {
|
|
36109
|
+
peerSigOk = false;
|
|
36110
|
+
}
|
|
36111
|
+
if (!peerSigOk) {
|
|
36112
|
+
console.warn("[fetchproxy] peer hello signature invalid \u2014 refusing registration (possible squatter)");
|
|
36113
|
+
ws.close(1008, "peer hello signature invalid");
|
|
36114
|
+
return;
|
|
36115
|
+
}
|
|
36116
|
+
const existing = peers.get(frame.mcpId);
|
|
36117
|
+
if (existing && existing.ws !== ws) {
|
|
36118
|
+
const existingEdPub = existing.helloFrame.identityEd25519Pub;
|
|
36119
|
+
if (existingEdPub !== frame.identityEd25519Pub) {
|
|
36120
|
+
console.warn("[fetchproxy] peer mcpId already mapped to a different identity \u2014 refusing (mcpId squatting)");
|
|
36121
|
+
ws.close(1008, "mcpId already registered to another identity");
|
|
36122
|
+
return;
|
|
36123
|
+
}
|
|
36124
|
+
}
|
|
36040
36125
|
identified = "peer";
|
|
36041
36126
|
peerMcpId = frame.mcpId;
|
|
36042
36127
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
@@ -36129,8 +36214,10 @@ async function startHost(opts) {
|
|
|
36129
36214
|
resetSessionPromise();
|
|
36130
36215
|
disconnectListeners.forEach((cb) => cb());
|
|
36131
36216
|
}
|
|
36132
|
-
if (identified === "peer" && peerMcpId)
|
|
36133
|
-
peers.
|
|
36217
|
+
if (identified === "peer" && peerMcpId) {
|
|
36218
|
+
if (peers.get(peerMcpId)?.ws === ws)
|
|
36219
|
+
peers.delete(peerMcpId);
|
|
36220
|
+
}
|
|
36134
36221
|
});
|
|
36135
36222
|
});
|
|
36136
36223
|
return {
|
|
@@ -36146,7 +36233,10 @@ async function startHost(opts) {
|
|
|
36146
36233
|
});
|
|
36147
36234
|
}),
|
|
36148
36235
|
sendOwnInner: async (inner) => {
|
|
36149
|
-
const session = await ownSessionReady
|
|
36236
|
+
const session = await awaitSessionReady(ownSessionReady, {
|
|
36237
|
+
mcpId: opts.ownMcpId,
|
|
36238
|
+
pendingPairCode: () => ownPendingPairCode
|
|
36239
|
+
});
|
|
36150
36240
|
if (!extensionWs)
|
|
36151
36241
|
throw new Error("host: no extension connected");
|
|
36152
36242
|
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), inner);
|
|
@@ -36251,7 +36341,10 @@ async function startPeer(opts) {
|
|
|
36251
36341
|
ws,
|
|
36252
36342
|
session: sessionPromise,
|
|
36253
36343
|
sendInner: async (inner) => {
|
|
36254
|
-
await sessionPromise
|
|
36344
|
+
await awaitSessionReady(sessionPromise, {
|
|
36345
|
+
mcpId: opts.mcpId,
|
|
36346
|
+
pendingPairCode: () => pendingPairCode
|
|
36347
|
+
});
|
|
36255
36348
|
const s = session;
|
|
36256
36349
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
36257
36350
|
ws.send(JSON.stringify(sealed));
|
|
@@ -36917,6 +37010,35 @@ var FetchproxyServer = class {
|
|
|
36917
37010
|
this.keepAliveTimer = null;
|
|
36918
37011
|
}
|
|
36919
37012
|
}
|
|
37013
|
+
/**
|
|
37014
|
+
* Send an inner request frame via whichever bridge handle is active. If the
|
|
37015
|
+
* send throws (e.g. `FetchproxySessionNotReadyError` — the session never
|
|
37016
|
+
* confirmed), the frame never reached the bridge, so no reply will arrive:
|
|
37017
|
+
* drop the just-registered pending resolver for this id (it lives in exactly
|
|
37018
|
+
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
37019
|
+
* server closes, then rethrow.
|
|
37020
|
+
*/
|
|
37021
|
+
async sendInnerFrame(inner) {
|
|
37022
|
+
try {
|
|
37023
|
+
if (this.hostHandle) {
|
|
37024
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
37025
|
+
} else if (this.peerHandle) {
|
|
37026
|
+
await this.peerHandle.sendInner(inner);
|
|
37027
|
+
}
|
|
37028
|
+
} catch (err) {
|
|
37029
|
+
if ("id" in inner && typeof inner.id === "number") {
|
|
37030
|
+
const { id } = inner;
|
|
37031
|
+
this.pending.delete(id);
|
|
37032
|
+
this.pendingReadCookies.delete(id);
|
|
37033
|
+
this.pendingStorage.delete(id);
|
|
37034
|
+
this.pendingCapture.delete(id);
|
|
37035
|
+
this.pendingRedirect.delete(id);
|
|
37036
|
+
this.pendingDownload.delete(id);
|
|
37037
|
+
this.pendingIdb.delete(id);
|
|
37038
|
+
}
|
|
37039
|
+
throw err;
|
|
37040
|
+
}
|
|
37041
|
+
}
|
|
36920
37042
|
/**
|
|
36921
37043
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
36922
37044
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
@@ -36928,11 +37050,7 @@ var FetchproxyServer = class {
|
|
|
36928
37050
|
const pending = new Promise((resolve2) => {
|
|
36929
37051
|
this.pending.set(id, resolve2);
|
|
36930
37052
|
});
|
|
36931
|
-
|
|
36932
|
-
await this.hostHandle.sendOwnInner(inner);
|
|
36933
|
-
} else if (this.peerHandle) {
|
|
36934
|
-
await this.peerHandle.sendInner(inner);
|
|
36935
|
-
}
|
|
37053
|
+
await this.sendInnerFrame(inner);
|
|
36936
37054
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
36937
37055
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
36938
37056
|
return pending;
|
|
@@ -36961,6 +37079,51 @@ var FetchproxyServer = class {
|
|
|
36961
37079
|
clearTimeout(timer);
|
|
36962
37080
|
}
|
|
36963
37081
|
}
|
|
37082
|
+
/**
|
|
37083
|
+
* FP-B2: bound a non-`fetch` verb's reply wait by `fetchTimeoutMs`.
|
|
37084
|
+
*
|
|
37085
|
+
* Before this, only `fetch()` raced its `pending` reply against a timer
|
|
37086
|
+
* (`_fetchOnceWithTimeout`); `readCookies`, the storage reads,
|
|
37087
|
+
* `capture_request_header`, `capture_redirect`, `download`, and
|
|
37088
|
+
* `read_indexed_db` awaited their `pending` promise with no race, so a
|
|
37089
|
+
* wedged extension hung the tool call indefinitely.
|
|
37090
|
+
*
|
|
37091
|
+
* `pending` is the already-registered reply promise. `pendingMap`/`id`
|
|
37092
|
+
* point at the op-specific map entry so we can drop it on expiry exactly
|
|
37093
|
+
* as `_fetchOnceWithTimeout` does — otherwise a late bridge reply would
|
|
37094
|
+
* resolve into a stale resolver / leak. On timeout we reject with a
|
|
37095
|
+
* `FetchproxyTimeoutError` (the same throwable the convenience methods
|
|
37096
|
+
* already surface for fetch timeouts). `0`/unset opts out (unbounded),
|
|
37097
|
+
* matching the fetch path.
|
|
37098
|
+
*/
|
|
37099
|
+
async _withVerbTimeout(pending, pendingMap, id, url2) {
|
|
37100
|
+
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37101
|
+
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37102
|
+
return pending;
|
|
37103
|
+
let timer;
|
|
37104
|
+
const start = Date.now();
|
|
37105
|
+
try {
|
|
37106
|
+
return await Promise.race([
|
|
37107
|
+
pending,
|
|
37108
|
+
new Promise((_resolve, reject) => {
|
|
37109
|
+
timer = setTimeout(() => {
|
|
37110
|
+
pendingMap.delete(id);
|
|
37111
|
+
reject(new FetchproxyTimeoutError({
|
|
37112
|
+
url: url2,
|
|
37113
|
+
timeoutMs,
|
|
37114
|
+
role: this.role,
|
|
37115
|
+
port: this.opts.port,
|
|
37116
|
+
elapsedMs: Date.now() - start,
|
|
37117
|
+
retryAttempted: false
|
|
37118
|
+
}));
|
|
37119
|
+
}, timeoutMs);
|
|
37120
|
+
})
|
|
37121
|
+
]);
|
|
37122
|
+
} finally {
|
|
37123
|
+
if (timer)
|
|
37124
|
+
clearTimeout(timer);
|
|
37125
|
+
}
|
|
37126
|
+
}
|
|
36964
37127
|
/**
|
|
36965
37128
|
* Map an `ok:false` fetch result to its typed throwable. Centralizes
|
|
36966
37129
|
* the kind-to-error-class switch so `request()` and (via the same
|
|
@@ -37251,12 +37414,8 @@ var FetchproxyServer = class {
|
|
|
37251
37414
|
const pending = new Promise((resolve2) => {
|
|
37252
37415
|
this.pendingReadCookies.set(id, resolve2);
|
|
37253
37416
|
});
|
|
37254
|
-
|
|
37255
|
-
|
|
37256
|
-
} else if (this.peerHandle) {
|
|
37257
|
-
await this.peerHandle.sendInner(inner);
|
|
37258
|
-
}
|
|
37259
|
-
const result = await pending;
|
|
37417
|
+
await this.sendInnerFrame(inner);
|
|
37418
|
+
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37260
37419
|
if (!result.ok) {
|
|
37261
37420
|
throw new FetchproxyProtocolError(result.error);
|
|
37262
37421
|
}
|
|
@@ -37322,12 +37481,8 @@ var FetchproxyServer = class {
|
|
|
37322
37481
|
const pending = new Promise((resolve2, reject) => {
|
|
37323
37482
|
this.pendingStorage.set(id, { resolve: resolve2, reject });
|
|
37324
37483
|
});
|
|
37325
|
-
|
|
37326
|
-
|
|
37327
|
-
} else if (this.peerHandle) {
|
|
37328
|
-
await this.peerHandle.sendInner(inner);
|
|
37329
|
-
}
|
|
37330
|
-
return pending;
|
|
37484
|
+
await this.sendInnerFrame(inner);
|
|
37485
|
+
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
37331
37486
|
}
|
|
37332
37487
|
/**
|
|
37333
37488
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
@@ -37431,12 +37586,8 @@ var FetchproxyServer = class {
|
|
|
37431
37586
|
const pending = new Promise((resolve2, reject) => {
|
|
37432
37587
|
this.pendingCapture.set(id, { resolve: resolve2, reject });
|
|
37433
37588
|
});
|
|
37434
|
-
|
|
37435
|
-
|
|
37436
|
-
} else if (this.peerHandle) {
|
|
37437
|
-
await this.peerHandle.sendInner(inner);
|
|
37438
|
-
}
|
|
37439
|
-
return pending;
|
|
37589
|
+
await this.sendInnerFrame(inner);
|
|
37590
|
+
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37440
37591
|
}
|
|
37441
37592
|
/**
|
|
37442
37593
|
* Snapshot the redirect target URL of the next request the browser
|
|
@@ -37520,12 +37671,8 @@ var FetchproxyServer = class {
|
|
|
37520
37671
|
const pending = new Promise((resolve2, reject) => {
|
|
37521
37672
|
this.pendingRedirect.set(id, { resolve: resolve2, reject });
|
|
37522
37673
|
});
|
|
37523
|
-
|
|
37524
|
-
|
|
37525
|
-
} else if (this.peerHandle) {
|
|
37526
|
-
await this.peerHandle.sendInner(inner);
|
|
37527
|
-
}
|
|
37528
|
-
return pending;
|
|
37674
|
+
await this.sendInnerFrame(inner);
|
|
37675
|
+
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37529
37676
|
}
|
|
37530
37677
|
/**
|
|
37531
37678
|
* Download `url` through the BROWSER's own network stack via
|
|
@@ -37606,12 +37753,8 @@ var FetchproxyServer = class {
|
|
|
37606
37753
|
const pending = new Promise((resolve2, reject) => {
|
|
37607
37754
|
this.pendingDownload.set(id, { resolve: resolve2, reject });
|
|
37608
37755
|
});
|
|
37609
|
-
|
|
37610
|
-
|
|
37611
|
-
} else if (this.peerHandle) {
|
|
37612
|
-
await this.peerHandle.sendInner(inner);
|
|
37613
|
-
}
|
|
37614
|
-
return pending;
|
|
37756
|
+
await this.sendInnerFrame(inner);
|
|
37757
|
+
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
37615
37758
|
}
|
|
37616
37759
|
/**
|
|
37617
37760
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
@@ -37658,12 +37801,8 @@ var FetchproxyServer = class {
|
|
|
37658
37801
|
const pending = new Promise((resolve2, reject) => {
|
|
37659
37802
|
this.pendingIdb.set(id, { resolve: resolve2, reject });
|
|
37660
37803
|
});
|
|
37661
|
-
|
|
37662
|
-
|
|
37663
|
-
} else if (this.peerHandle) {
|
|
37664
|
-
await this.peerHandle.sendInner(inner);
|
|
37665
|
-
}
|
|
37666
|
-
return pending;
|
|
37804
|
+
await this.sendInnerFrame(inner);
|
|
37805
|
+
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
37667
37806
|
}
|
|
37668
37807
|
assertScopeSubset(requested, declared, label) {
|
|
37669
37808
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -38150,7 +38289,7 @@ function getDefaultInlineAttachments() {
|
|
|
38150
38289
|
// package.json
|
|
38151
38290
|
var package_default = {
|
|
38152
38291
|
name: "ofw-mcp",
|
|
38153
|
-
version: "2.4.
|
|
38292
|
+
version: "2.4.3",
|
|
38154
38293
|
license: "MIT",
|
|
38155
38294
|
mcpName: "io.github.chrischall/ofw-mcp",
|
|
38156
38295
|
description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
@@ -38182,7 +38321,7 @@ var package_default = {
|
|
|
38182
38321
|
"test:watch": "vitest"
|
|
38183
38322
|
},
|
|
38184
38323
|
dependencies: {
|
|
38185
|
-
"@chrischall/mcp-utils": "^0.
|
|
38324
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
38186
38325
|
"@fetchproxy/bootstrap": "^1.3.0",
|
|
38187
38326
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38188
38327
|
dotenv: "^17.4.2",
|
|
@@ -38261,26 +38400,7 @@ async function resolveAuth() {
|
|
|
38261
38400
|
);
|
|
38262
38401
|
}
|
|
38263
38402
|
|
|
38264
|
-
// src/env-bootstrap.ts
|
|
38265
|
-
var USER_CONFIG_KEYS = [
|
|
38266
|
-
"OFW_USERNAME",
|
|
38267
|
-
"OFW_PASSWORD",
|
|
38268
|
-
"OFW_INLINE_ATTACHMENTS",
|
|
38269
|
-
"OFW_ATTACHMENTS_DIR",
|
|
38270
|
-
"OFW_WRITE_MODE"
|
|
38271
|
-
];
|
|
38272
|
-
function clearBlankInjectedEnv(env = process.env, keys = USER_CONFIG_KEYS) {
|
|
38273
|
-
for (const key of keys) {
|
|
38274
|
-
const value = env[key];
|
|
38275
|
-
if (value === void 0) continue;
|
|
38276
|
-
if (value.trim() === "" || value.includes("${")) {
|
|
38277
|
-
delete env[key];
|
|
38278
|
-
}
|
|
38279
|
-
}
|
|
38280
|
-
}
|
|
38281
|
-
|
|
38282
38403
|
// src/client.ts
|
|
38283
|
-
clearBlankInjectedEnv();
|
|
38284
38404
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38285
38405
|
await loadDotenvSafely({ path: join4(__dirname, "..", ".env") });
|
|
38286
38406
|
function parseContentDispositionFilename(cd) {
|
|
@@ -39806,7 +39926,7 @@ process.emit = function(event, ...args) {
|
|
|
39806
39926
|
};
|
|
39807
39927
|
await runMcp({
|
|
39808
39928
|
name: "ofw",
|
|
39809
|
-
version: "2.4.
|
|
39929
|
+
version: "2.4.3",
|
|
39810
39930
|
// x-release-please-version
|
|
39811
39931
|
deps: client,
|
|
39812
39932
|
tools: [
|
package/dist/client.js
CHANGED
|
@@ -4,13 +4,7 @@ import { dirname, join } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { resolveAuth } from './auth.js';
|
|
6
6
|
import { parseBoolEnv } from './config.js';
|
|
7
|
-
import { clearBlankInjectedEnv } from './env-bootstrap.js';
|
|
8
7
|
import { BASE_URL, OFW_PROTOCOL_HEADERS, OFW_TOKEN_TTL_MS, OFW_TOKEN_EXPIRY_SKEW_MS } from './protocol.js';
|
|
9
|
-
// When the plugin runs via a host that maps creds from optional
|
|
10
|
-
// `${user_config.*}` fields, an unset field can be injected as a blank /
|
|
11
|
-
// placeholder env value. Clear those first so the next step's .env (and the
|
|
12
|
-
// shell env) can still populate them — a filled field keeps its real value.
|
|
13
|
-
clearBlankInjectedEnv();
|
|
14
8
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb
|
|
15
9
|
// bundle). loadDotenvSafely applies override:false + quiet:true and swallows a
|
|
16
10
|
// missing dotenv module, matching the prior inline try/catch exactly.
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import { registerJournalTools } from './tools/journal.js';
|
|
|
24
24
|
// always succeeds before any credential check runs.
|
|
25
25
|
await runMcp({
|
|
26
26
|
name: 'ofw',
|
|
27
|
-
version: '2.4.
|
|
27
|
+
version: '2.4.3', // x-release-please-version
|
|
28
28
|
deps: client,
|
|
29
29
|
tools: [
|
|
30
30
|
registerUserTools,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"mcpName": "io.github.chrischall/ofw-mcp",
|
|
6
6
|
"description": "OurFamilyWizard MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test:watch": "vitest"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@chrischall/mcp-utils": "^0.
|
|
35
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
36
36
|
"@fetchproxy/bootstrap": "^1.3.0",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
38
|
"dotenv": "^17.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.4.
|
|
9
|
+
"version": "2.4.3",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.4.
|
|
14
|
+
"version": "2.4.3",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/dist/env-bootstrap.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Credentials + config the plugin's .mcp.json maps from `${user_config.*}`.
|
|
2
|
-
// These are the keys whose host-injected values we sanity-check before
|
|
3
|
-
// loading .env.
|
|
4
|
-
export const USER_CONFIG_KEYS = [
|
|
5
|
-
'OFW_USERNAME',
|
|
6
|
-
'OFW_PASSWORD',
|
|
7
|
-
'OFW_INLINE_ATTACHMENTS',
|
|
8
|
-
'OFW_ATTACHMENTS_DIR',
|
|
9
|
-
'OFW_WRITE_MODE',
|
|
10
|
-
];
|
|
11
|
-
// A host that maps an UNSET optional `${user_config.x}` into the server env
|
|
12
|
-
// may inject the key as an empty string or the literal, unexpanded
|
|
13
|
-
// "${user_config.x}". Either one SHADOWS the user's `.env`/shell value,
|
|
14
|
-
// because the server loads .env with dotenv `override:false` (it won't
|
|
15
|
-
// replace a key that's already present). Clearing those blanks first lets the
|
|
16
|
-
// `.env`/shell credential path keep working when the Connectors field is left
|
|
17
|
-
// empty — while a real, user-provided value is left untouched (so the desktop
|
|
18
|
-
// userConfig path still wins when set).
|
|
19
|
-
export function clearBlankInjectedEnv(env = process.env, keys = USER_CONFIG_KEYS) {
|
|
20
|
-
for (const key of keys) {
|
|
21
|
-
const value = env[key];
|
|
22
|
-
if (value === undefined)
|
|
23
|
-
continue;
|
|
24
|
-
if (value.trim() === '' || value.includes('${')) {
|
|
25
|
-
delete env[key];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|