ofw-mcp 2.20.2 → 2.20.4

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "OurFamilyWizard tools for Claude Code",
9
- "version": "2.20.2"
9
+ "version": "2.20.4"
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.2",
17
+ "version": "2.20.4",
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.2",
4
+ "version": "2.20.4",
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
- "mcp": "./.mcp.json"
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) | Auto | any |
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 |
@@ -227,13 +227,14 @@ Every list read also carries an explicit **`complete`** boolean describing the *
227
227
 
228
228
  ### Server-side confirmation (`MCP_CONFIRM_MODE`)
229
229
 
230
- The writes marked **Confirm (server)** above — sending a message, logging an expense, creating/updating/deleting a *shared* calendar event, and uploading a file as `SHARED` — are confirmed by this server, not left to the host. A client that can show a confirmation prompt (Claude Code) gets one, with a preview of exactly what would happen. A client that cannot (claude.ai, Claude Desktop) gets two steps: the first call writes **nothing** and returns that preview plus a `confirmToken`, and only a repeat call with the same arguments and that token proceeds.
230
+ The writes marked **Confirm (server)** above — sending a message, logging an expense, creating/updating/deleting a *shared* calendar event, and uploading a file as `SHARED` — are confirmed by this server, not left to the host. A client that can show a confirmation prompt (Claude Code) gets one, with a preview of exactly what would happen, unless `MCP_CONFIRM_ELICITATION=off`. A client that cannot (claude.ai, Claude Desktop) gets two steps: the first call writes **nothing** and returns that preview plus a `confirmToken`, and only a repeat call with the same arguments and that token proceeds.
231
231
 
232
232
  Previews name what you are approving — recipients by name, subject and full body, the reply target, attachment file names; the expense amount and description; the event's title, date, time, visibility and (for an update) before and after. The token is bound to exactly that: a different body, amount or recipient is refused, and so is a draft or event that changed on OurFamilyWizard after the preview (say, the co-parent edited the event), even with `force: true`. A token works once and expires.
233
233
 
234
234
  | Variable | Default | |
235
235
  |---|---|---|
236
236
  | `MCP_CONFIRM_MODE` | `ask-user` | What a gated write does on a client that cannot show a prompt. `ask-user`: two steps, and the model must get your approval in chat before using the token. `auto`: two steps, but the model may use the token after reviewing the preview itself. `refuse`: such writes are refused (do them on ourfamilywizard.com). An unrecognised value is treated as `refuse`. |
237
+ | `MCP_CONFIRM_ELICITATION` | `on` | `off` never shows a confirmation prompt, so every client gets the `MCP_CONFIRM_MODE` path. Set it for a client that claims to support prompts but never shows one (the gated write hangs — opencode 2.0.x). Any other value stays `on`, with a warning on stderr. |
237
238
  | `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
238
239
  | `MCP_CONFIRM_SECRET` | random per process | Signing key; set it only if tokens must survive a server restart. On mcp-host the host supplies a stable per-child key (`MCP_HOST_CONFIRM_SECRET`) and spent tokens are recorded under `MCP_DATA_DIR`, so an approval survives an idle restart. |
239
240
 
@@ -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
- const data = (await response.json());
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: data.auth,
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 pkg from '../package.json' with { type: 'json' };
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: pkg.name,
128
- version: pkg.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.