ofw-mcp 2.10.2 → 2.12.0
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 +1 -1
- package/dist/auth.js +86 -56
- package/dist/bundle.js +740 -228
- package/dist/cache/store.js +6 -1
- package/dist/client.js +24 -12
- package/dist/index.js +1 -1
- package/dist/session-cache.js +78 -0
- package/dist/tools/expenses.js +21 -3
- package/dist/tools/journal.js +21 -3
- package/dist/tools/messages.js +60 -6
- package/dist/tools/pagination.js +120 -0
- package/mint.yaml +143 -0
- package/package.json +8 -6
- package/server.json +14 -2
- package/skills/ofw/SKILL.md +5 -4
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.12.0"
|
|
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.
|
|
17
|
+
"version": "2.12.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Chris Chall"
|
|
20
20
|
},
|
package/README.md
CHANGED
|
@@ -275,7 +275,7 @@ Calendar events sit between the two message tiers: they have no draft stage (a c
|
|
|
275
275
|
## Development
|
|
276
276
|
|
|
277
277
|
```bash
|
|
278
|
-
npm test #
|
|
278
|
+
npm test # tsc typecheck, then the vitest suite
|
|
279
279
|
npm run build # tsc → dist/, then esbuild bundle → dist/bundle.js
|
|
280
280
|
npm run dev # node --env-file=.env dist/index.js (requires built dist)
|
|
281
281
|
```
|
package/dist/auth.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// Auth resolution — Pattern A
|
|
2
|
+
// Auth resolution — Pattern A, on the shared resolver
|
|
3
3
|
// ────────────────────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
|
-
// This file
|
|
6
|
-
//
|
|
7
|
-
// (
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// This file used to BE the canonical shape, and said so: the other MCPs in
|
|
6
|
+
// this family were told to model their resolution on it. That shape now lives
|
|
7
|
+
// in `resolveAuthPattern` (@chrischall/mcp-utils), which owns the one thing
|
|
8
|
+
// every copy of it had to agree on — the priority order — and nothing else.
|
|
9
|
+
// What stays here is what is genuinely OFW's: how each path gets a token, and
|
|
10
|
+
// what to say when it cannot.
|
|
10
11
|
//
|
|
11
|
-
//
|
|
12
|
+
// The migration waited on somewhere to put the expiry. The shared result
|
|
13
|
+
// carried `{ credential, source }` only, so adopting it meant discarding the
|
|
14
|
+
// `tokenExpiry` this file reads from the browser tab — which is the whole
|
|
15
|
+
// reason it reads it. `PatternResult.expiresAt` closed that (mcp-utils#148).
|
|
12
16
|
//
|
|
13
|
-
//
|
|
17
|
+
// THE THREE PATHS, in priority order — now expressed by which resolvers are
|
|
18
|
+
// PROVIDED, since `resolveAuthPattern` runs the first configured one:
|
|
19
|
+
//
|
|
20
|
+
// 1. Env-var credentials (`sessionScrape`)
|
|
14
21
|
// OFW_USERNAME + OFW_PASSWORD set → POST the login form, get a token.
|
|
15
22
|
// This is the legacy path. It runs unchanged when both vars are set
|
|
16
23
|
// so existing users (Claude Desktop with mcpb env config, etc.) are
|
|
@@ -45,7 +52,7 @@
|
|
|
45
52
|
// - `./auth-password.js` (loginWithPassword) is a separate module
|
|
46
53
|
// specifically so it can be mocked here too. This keeps the
|
|
47
54
|
// selection logic independent of either implementation.
|
|
48
|
-
import { parseBoolEnv, readEnvVar } from '@chrischall/mcp-utils';
|
|
55
|
+
import { parseBoolEnv, readEnvVar, resolveAuthPattern } from '@chrischall/mcp-utils';
|
|
49
56
|
import { bootstrap } from '@fetchproxy/bootstrap';
|
|
50
57
|
import { classifyBridgeError } from '@chrischall/mcp-utils/fetchproxy';
|
|
51
58
|
import { loginWithPassword } from './auth-password.js';
|
|
@@ -63,62 +70,85 @@ function fetchproxyDisabled() {
|
|
|
63
70
|
* The field exists for logging / future cache-keying only.
|
|
64
71
|
*/
|
|
65
72
|
export async function resolveAuth() {
|
|
66
|
-
//
|
|
73
|
+
// Which paths are CONFIGURED. `resolveAuthPattern` runs the first one
|
|
74
|
+
// provided, in the fleet's fixed priority order (token → oauth →
|
|
75
|
+
// sessionScrape → fetchproxy), so "env beats fetchproxy" is expressed by
|
|
76
|
+
// which keys exist rather than by an if/else here.
|
|
77
|
+
const pattern = {};
|
|
78
|
+
// ── Path 1 (sessionScrape): env-var credentials.
|
|
67
79
|
// `readEnvVar` trims and treats blank / `"undefined"` / `"null"` /
|
|
68
80
|
// `${UNEXPANDED}` placeholders as unset — defends against MCP hosts that
|
|
69
81
|
// pass `.mcp.json` env blocks through without variable expansion.
|
|
70
82
|
const username = readEnvVar('OFW_USERNAME');
|
|
71
83
|
const password = readEnvVar('OFW_PASSWORD');
|
|
72
84
|
if (username && password) {
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
pattern.sessionScrape = async () => {
|
|
86
|
+
const { token, expiresAt } = await loginWithPassword(username, password);
|
|
87
|
+
return { credential: token, source: 'env', expiresAt };
|
|
88
|
+
};
|
|
75
89
|
}
|
|
76
|
-
// ── Path 2
|
|
90
|
+
// ── Path 2 (fetchproxy): lift the session out of a signed-in browser tab.
|
|
77
91
|
if (!fetchproxyDisabled()) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
pattern.fetchproxy = async () => {
|
|
93
|
+
try {
|
|
94
|
+
const session = await bootstrap({
|
|
95
|
+
serverName: pkg.name,
|
|
96
|
+
version: pkg.version,
|
|
97
|
+
// OFW serves both ofw.ourfamilywizard.com and www.ourfamilywizard.com;
|
|
98
|
+
// the API + auth token live on the apex. The extension matches on
|
|
99
|
+
// suffix, so listing the apex covers both.
|
|
100
|
+
domains: ['ourfamilywizard.com'],
|
|
101
|
+
declare: {
|
|
102
|
+
cookies: [],
|
|
103
|
+
// The web app stores the Bearer token in localStorage["auth"] and
|
|
104
|
+
// its expiry (ISO string) in localStorage["tokenExpiry"]. Mirroring
|
|
105
|
+
// both means our 401-replay logic can be slightly smarter, and the
|
|
106
|
+
// expiry surfaces correctly in diagnostics.
|
|
107
|
+
localStorage: ['auth', 'tokenExpiry'],
|
|
108
|
+
sessionStorage: [],
|
|
109
|
+
captureHeaders: [],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
const token = session.localStorage['auth'];
|
|
113
|
+
const expiryRaw = session.localStorage['tokenExpiry'];
|
|
114
|
+
if (!token) {
|
|
115
|
+
throw new Error('localStorage["auth"] missing on ourfamilywizard.com. ' +
|
|
116
|
+
'Sign into OFW in your browser (with the fetchproxy extension installed) and retry.');
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
credential: token,
|
|
120
|
+
source: 'fetchproxy',
|
|
121
|
+
...(expiryRaw ? { expiresAt: new Date(expiryRaw) } : {}),
|
|
122
|
+
};
|
|
102
123
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (classifyBridgeError(e) === 'bridge_down') {
|
|
112
|
-
const downErr = e;
|
|
113
|
-
throw new Error(`OFW auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
|
|
124
|
+
catch (e) {
|
|
125
|
+
// FetchproxyBridgeDownError only escapes bootstrap() after the lazy-revive retry fails — surface .hint verbatim (actionable "click toolbar icon" copy).
|
|
126
|
+
if (classifyBridgeError(e) === 'bridge_down') {
|
|
127
|
+
const downErr = e;
|
|
128
|
+
throw new Error(`OFW auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
|
|
129
|
+
}
|
|
130
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
131
|
+
throw new Error(`OFW auth: no OFW_USERNAME/OFW_PASSWORD set, and fetchproxy fallback failed: ${msg}`);
|
|
114
132
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ── Path 3: nothing configured. Raised HERE rather than letting
|
|
136
|
+
// `resolveAuthPattern` throw its generic "no auth configured" message,
|
|
137
|
+
// because this one names OFW's own two fixes side-by-side and the generic
|
|
138
|
+
// one cannot.
|
|
139
|
+
if (!pattern.sessionScrape && !pattern.fetchproxy) {
|
|
140
|
+
throw new Error('OFW auth: set OFW_USERNAME + OFW_PASSWORD, ' +
|
|
141
|
+
'or install the fetchproxy extension and sign into ourfamilywizard.com ' +
|
|
142
|
+
'(unset OFW_DISABLE_FETCHPROXY if it is set).');
|
|
118
143
|
}
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
// Errors from the winning path propagate UNWRAPPED, which is what keeps the
|
|
145
|
+
// bridge-down `.hint` above intact.
|
|
146
|
+
const { credential, source, expiresAt } = await resolveAuthPattern(pattern);
|
|
147
|
+
return {
|
|
148
|
+
token: credential,
|
|
149
|
+
// The pattern's `source` is a free-form string; ours is a two-value union,
|
|
150
|
+
// and both resolvers above set it to a member of that union.
|
|
151
|
+
source: source,
|
|
152
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
153
|
+
};
|
|
124
154
|
}
|