ofw-mcp 2.20.3 → 2.20.5
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 -2
- package/README.md +7 -3
- package/dist/auth-password.js +27 -2
- package/dist/auth.js +3 -3
- package/dist/bundle.js +375 -157
- package/dist/client.js +34 -5
- package/dist/config.js +3 -4
- package/dist/extract/inflate.js +36 -4
- package/dist/extract/pdf.js +8 -5
- package/dist/extract/zip.js +8 -3
- package/dist/index.js +2 -1
- package/dist/protocol.js +18 -8
- package/dist/tools/_confirm.js +4 -0
- package/dist/tools/_shared.js +14 -0
- package/dist/tools/calendar.js +9 -5
- package/dist/tools/expenses.js +11 -5
- package/dist/tools/healthcheck.js +17 -9
- package/dist/tools/journal.js +4 -2
- package/dist/tools/messages.js +42 -28
- package/dist/tools/user.js +7 -2
- package/dist/version.js +7 -0
- package/mint.yaml +6 -1
- package/package.json +4 -4
- package/server.json +92 -2
- package/skills/ofw/SKILL.md +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.20.
|
|
9
|
+
"version": "2.20.5"
|
|
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.20.
|
|
17
|
+
"version": "2.20.5",
|
|
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.20.
|
|
4
|
+
"version": "2.20.5",
|
|
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,5 +17,5 @@
|
|
|
17
17
|
"family"
|
|
18
18
|
],
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"
|
|
20
|
+
"mcpServers": "./.mcp.json"
|
|
21
21
|
}
|
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ Read-only tools run automatically. Writes that reach your co-parent or the court
|
|
|
131
131
|
| Tool | What it does | Permission | Write mode |
|
|
132
132
|
|------|-------------|------------|------------|
|
|
133
133
|
| `ofw_get_profile` | Your profile and co-parent info | Auto | any |
|
|
134
|
-
| `ofw_get_notifications` | Dashboard counts (unread messages, upcoming events, outstanding expenses) |
|
|
134
|
+
| `ofw_get_notifications` | Dashboard counts (unread messages, upcoming events, outstanding expenses). Updates your co-parent-visible last-seen status, so it is not marked read-only | Confirm | any |
|
|
135
135
|
| `ofw_list_message_folders` | Folders with unread counts — **get folder IDs here before listing messages** | Auto | any |
|
|
136
136
|
| `ofw_list_messages` | Messages in a folder | Auto | any |
|
|
137
137
|
| `ofw_get_message` | Full content of a single message | Auto | any |
|
|
@@ -293,7 +293,9 @@ Calendar events sit between the two message tiers: they have no draft stage (a c
|
|
|
293
293
|
|
|
294
294
|
### Egress allowlist
|
|
295
295
|
|
|
296
|
-
Every
|
|
296
|
+
Every request that carries your OFW token or login passes its constructed URL through a host check before `fetch` — the server refuses to send them to any host other than `ofw.ourfamilywizard.com`. Today it always passes, and that is the point: it makes "the credential only ever goes to OFW" a structural invariant rather than a code-review promise, so a later refactor or a compromised dependency that pointed a request elsewhere throws instead of carrying your bearer token or messages off-host. It is a pre-flight check on the URL we build, not a redirect-following egress filter.
|
|
297
|
+
|
|
298
|
+
The one other host the server contacts is `*.oaiusercontent.com`, and only for `ofw_upload_expense_pdf`'s `url` mode: it downloads the receipt PDF from that signed file URL with no OFW credential attached, re-checking every redirect hop against the same suffix, capping the size and requiring a real PDF. The hosted registration's egress allowlist (`mint.yaml`) names exactly these two hosts.
|
|
297
299
|
|
|
298
300
|
## Troubleshooting
|
|
299
301
|
|
|
@@ -317,7 +319,7 @@ Every outbound request passes its constructed URL through a host check before `f
|
|
|
317
319
|
- They are passed to the server as environment variables and never logged
|
|
318
320
|
- The server authenticates with OFW using the same login flow as the web app
|
|
319
321
|
- Use a strong, unique OFW password
|
|
320
|
-
-
|
|
322
|
+
- Requests carrying your OFW credential are host-allowlisted to `ofw.ourfamilywizard.com`; the only other host contacted is the receipt-download host for `ofw_upload_expense_pdf`'s `url` mode (see [Egress allowlist](#egress-allowlist))
|
|
321
323
|
|
|
322
324
|
### Local data, and how to remove it
|
|
323
325
|
|
|
@@ -339,6 +341,8 @@ rm -f ~/.ofw-mcp/session.json # or your OFW_SESSION_FILE
|
|
|
339
341
|
|
|
340
342
|
The next start rebuilds the cache from OurFamilyWizard with `ofw_sync_messages`. A directory you pointed `OFW_ATTACHMENTS_DIR` at yourself keeps its own permissions (it may be shared on purpose); only what the server creates, and the default `~/Downloads/ofw-mcp`, are locked down.
|
|
341
343
|
|
|
344
|
+
**On a hosted connector (mcp-host)** the same data lives in the registration's persistent data directory instead of on your machine: the message cache under `$HOME/.cache/ofw-mcp/`, the session token at `$MCP_DATA_DIR/.ofw-mcp/session.json`, the browser-bridge identity under `$HOME/.fetchproxy/identity/`, and spent confirm-token nonces under `$MCP_DATA_DIR/.mcp-confirm/`. Downloads are returned inline and never written to disk. The commands above do not reach it — how long that directory is kept, and whether disconnecting or deleting the registration removes it, is the host's retention policy, not this server's.
|
|
345
|
+
|
|
342
346
|
## Development
|
|
343
347
|
|
|
344
348
|
```bash
|
package/dist/auth-password.js
CHANGED
|
@@ -118,9 +118,34 @@ export async function loginWithPassword(username, password) {
|
|
|
118
118
|
const body = await response.text();
|
|
119
119
|
throw new Error(`OFW login returned unexpected response (${contentType || 'no content-type'}): ${body.substring(0, 200)}`);
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
// A 200 JSON answer is not proof of a token: a lockout, MFA or captcha
|
|
122
|
+
// challenge can arrive the same way. Without this check the token would be
|
|
123
|
+
// the string "undefined", every request would 401, and the TokenManager
|
|
124
|
+
// would re-mint through another password POST — one OFW counts against the
|
|
125
|
+
// account. Not latched: this is not OFW judging the password.
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
const record = typeof data === 'object' && data !== null ? data : null;
|
|
128
|
+
const token = record?.auth;
|
|
129
|
+
if (typeof token !== 'string' || token === '') {
|
|
130
|
+
throw new Error(describeTokenlessLogin(record));
|
|
131
|
+
}
|
|
122
132
|
return {
|
|
123
|
-
token
|
|
133
|
+
token,
|
|
124
134
|
expiresAt: new Date(Date.now() + OFW_TOKEN_TTL_MS),
|
|
125
135
|
};
|
|
126
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* An actionable message for a login answer that carried no token. It names
|
|
139
|
+
* the response's KEYS and OFW's own `message` text, never any other value —
|
|
140
|
+
* a challenge payload can carry ids or tokens that do not belong in an error.
|
|
141
|
+
*/
|
|
142
|
+
function describeTokenlessLogin(record) {
|
|
143
|
+
const base = 'OFW login returned no token, so it did not sign in';
|
|
144
|
+
if (record === null) {
|
|
145
|
+
return `${base} (the response was not a JSON object). Sign in to OurFamilyWizard in a browser to check the account, then try again.`;
|
|
146
|
+
}
|
|
147
|
+
const keys = Object.keys(record);
|
|
148
|
+
const said = typeof record.message === 'string' ? ` OFW said: "${record.message.substring(0, 200)}".` : '';
|
|
149
|
+
return `${base} (response fields: ${keys.length > 0 ? keys.join(', ') : 'none'}).${said} `
|
|
150
|
+
+ 'The account may be locked or need a verification step: sign in to OurFamilyWizard in a browser to clear it, then try again.';
|
|
151
|
+
}
|
package/dist/auth.js
CHANGED
|
@@ -56,7 +56,7 @@ import { parseBoolEnv, readEnvVar, resolveAuthPattern } from '@chrischall/mcp-ut
|
|
|
56
56
|
import { bootstrap } from '@fetchproxy/bootstrap';
|
|
57
57
|
import { classifyBridgeError } from '@chrischall/mcp-utils/fetchproxy';
|
|
58
58
|
import { loginWithPassword } from './auth-password.js';
|
|
59
|
-
import
|
|
59
|
+
import { SERVER_NAME, VERSION } from './version.js';
|
|
60
60
|
/** True if the user has explicitly disabled the fetchproxy fallback. */
|
|
61
61
|
function fetchproxyDisabled() {
|
|
62
62
|
return parseBoolEnv('OFW_DISABLE_FETCHPROXY');
|
|
@@ -124,8 +124,8 @@ export async function resolveAuth() {
|
|
|
124
124
|
pattern.fetchproxy = async () => {
|
|
125
125
|
try {
|
|
126
126
|
const session = await bootstrap({
|
|
127
|
-
serverName:
|
|
128
|
-
version:
|
|
127
|
+
serverName: SERVER_NAME,
|
|
128
|
+
version: VERSION,
|
|
129
129
|
// OFW serves both ofw.ourfamilywizard.com and www.ourfamilywizard.com;
|
|
130
130
|
// the API + auth token live on the apex. The extension matches on
|
|
131
131
|
// suffix, so listing the apex covers both.
|