ofw-mcp 2.8.0 → 2.9.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 +44 -3
- package/dist/bundle.js +644 -118
- package/dist/cache/store.js +79 -1
- package/dist/config.js +18 -0
- package/dist/index.js +1 -1
- package/dist/sync.js +4 -0
- package/dist/tools/draft-freshness.js +46 -13
- package/dist/tools/lifecycle.js +277 -0
- package/dist/tools/messages.js +447 -135
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/ofw/SKILL.md +32 -13
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.9.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.9.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Chris Chall"
|
|
20
20
|
},
|
package/README.md
CHANGED
|
@@ -148,7 +148,8 @@ Read-only tools run automatically. Write tools ask for your confirmation first.
|
|
|
148
148
|
| `ofw_get_message` | Full content of a single message | Auto | any |
|
|
149
149
|
| `ofw_sync_messages` | Sync messages into the local cache (unread bodies left unfetched to avoid read receipts) | Auto | any |
|
|
150
150
|
| `ofw_get_unread_sent` | Sent messages a recipient hasn't read yet (from local cache) | Auto | any |
|
|
151
|
-
| `ofw_check_freshness` | Cheap live check that the cache still matches OFW —
|
|
151
|
+
| `ofw_check_freshness` | Cheap live check that the cache still matches OFW — per id, whether it is still a `draft` or was `sent`/`deleted`, without a full sync | Auto | any |
|
|
152
|
+
| `ofw_status` | **One live call for "where does everything stand?"** — the full verified draft inventory, and the current state of any ids or draft keys | Auto | any |
|
|
152
153
|
| `ofw_download_attachment` | Download a message attachment to disk, or inline as extracted content / bytes | Auto | any |
|
|
153
154
|
| `ofw_send_message` | Send a message | Confirm | `all` |
|
|
154
155
|
| `ofw_list_drafts` | Draft messages | Auto | any |
|
|
@@ -188,10 +189,50 @@ So every read tool (`ofw_list_messages`, `ofw_list_drafts`, `ofw_get_message`, `
|
|
|
188
189
|
|
|
189
190
|
Drafts additionally carry per-item `cacheStatus`, `asOf`, and **`serverConfirmed`** — true only when a completed drafts walk verified them inside the threshold. `serverConfirmed: false` means a draft's existence and unsent status are *remembered*, not known, and should not be stated as current fact without calling `ofw_check_freshness` first.
|
|
190
191
|
|
|
191
|
-
`ofw_check_freshness` is the cheap way to re-verify: one request for a folder count comparison plus one per message id, no bodies, no full sync. Draft ids are compared by **content revision**, not timestamp, for the reason above.
|
|
192
|
+
`ofw_check_freshness` is the cheap way to re-verify: one request for a folder count comparison plus one per message id, no bodies, no full sync. Draft ids are compared by **content revision**, not timestamp, for the reason above. It probes ids that are cached as drafts, as sent messages, or as already-read inbox messages freely, because none of those can change anything; any other id would mark an unread inbox message as read on OurFamilyWizard — an irreversible change to a court-visible record — so those are skipped unless you pass `allowMarkRead: true`.
|
|
192
193
|
|
|
193
194
|
Set `OFW_FRESHNESS_TTL_SECONDS` to tune the threshold (default `300`, i.e. 5 minutes). Unusable values fall back to the default rather than widening the window.
|
|
194
195
|
|
|
196
|
+
### Is it still what I think it is? (`ofw_status`)
|
|
197
|
+
|
|
198
|
+
Freshness answers *how old is this data*. It does not answer *is this entity still what I think it is* — and that is a different failure. A draft that has been **sent** still exists on the server, so "does this id exist?" comes back `true` for the one case where the answer matters most.
|
|
199
|
+
|
|
200
|
+
`ofw_status` is the call that should back any status summary:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"checkedAt": "2026-07-28T09:12:00.000Z",
|
|
205
|
+
"requested": [
|
|
206
|
+
{ "id": 538279699, "state": "sent", "sentAt": "2026-07-27T23:31:09", "inSync": false },
|
|
207
|
+
{ "id": 538086428, "state": "draft", "inSync": true }
|
|
208
|
+
],
|
|
209
|
+
"complete": true
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- `state` is `draft`, `sent`, `received`, `deleted` or `unknown`, read live from OFW. `unknown` means the question was **not** answered — it is not a synonym for "fine".
|
|
214
|
+
- With no arguments it returns the **full** draft inventory, verified against OFW first.
|
|
215
|
+
- `complete: true` means every part of the snapshot was confirmed live. If it is false, `incompleteReasons` says what wasn't, and the payload is not a basis for stating a count.
|
|
216
|
+
|
|
217
|
+
**Draft keys.** Editing a draft mints a new OFW id every time (`ofw_save_draft` replaces by create-then-delete, because OFW's update-in-place endpoint silently no-ops). `ofw_save_draft` therefore also returns a `draftKey` that stays constant across every edit *and* follows the message into Sent. `ofw_status(draftKeys: ["dk_…"])` resolves it to the current id and state — including `state: "sent"` with `sentMessageId` — so "what happened to the draft I was working on?" is one call, not a guess about which id is current.
|
|
218
|
+
|
|
219
|
+
### Absence is never reported from a stale cache
|
|
220
|
+
|
|
221
|
+
A cached read that comes back **empty** is shaped identically to a verified "nothing there". `ofw_list_messages`, `ofw_list_drafts` and `ofw_get_unread_sent` therefore refuse rather than answer when the result is empty *and* the backing cache is not `fresh`:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"result": "UNVERIFIED_EMPTY",
|
|
226
|
+
"reason": "No drafts were found, but the backing cache is \"unverified\" — it was last verified 207 min ago. Refusing to report absence from unverified data…",
|
|
227
|
+
"remedy": "Call ofw_sync_messages(folders:[\"drafts\"]) and retry, re-call with autoRefresh:true, or use ofw_status(includeDraftInventory:true) for a single live answer.",
|
|
228
|
+
"complete": false
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A false negative ("no, that was never sent") is more dangerous than a refusal, because it reads as a definitive answer. Non-empty results are never withheld — a stale cache that *did* find something is still evidence of presence, labelled with its age as before. Pass `autoRefresh: true` (or set `OFW_AUTO_REFRESH=true`) to have the tool sync and answer instead of refusing; a refresh that still cannot make the read verifiable refuses anyway.
|
|
233
|
+
|
|
234
|
+
Every list read also carries an explicit **`complete`** boolean describing the *result set* — "this is every matching item on OurFamilyWizard as of `asOf`" — with a `completeNote` naming what is missing when it is false. Check it before stating a count.
|
|
235
|
+
|
|
195
236
|
### Write protection (`OFW_WRITE_MODE`)
|
|
196
237
|
|
|
197
238
|
The "Confirm" permission above is a *hint* to the MCP host — a host configured to auto-approve tools (or a user who clicked "always allow" once) would leave nothing between model output and a sent message. Because OurFamilyWizard is a court-of-record platform, the server also supports a structural gate: set `OFW_WRITE_MODE` in the server's `env` block and tools above your chosen level are **never registered**, so no host setting or prompt-injected instruction can invoke them.
|
|
@@ -213,7 +254,7 @@ By default nothing changes: reads behave exactly as they always have. Two contro
|
|
|
213
254
|
| Setting | Effect |
|
|
214
255
|
|---|---|
|
|
215
256
|
| `ofw_get_message(allowMarkRead: false)` | Refuses a fetch that would stamp an unread inbox message, returning a structured `MARK_READ_BLOCKED` payload instead. Cached bodies, sent messages and already-read messages still return normally — none of them can stamp anything. |
|
|
216
|
-
| `OFW_ALLOW_MARK_READ=false` | Deployment-wide ceiling. No tool may stamp: `ofw_get_message` refuses, `ofw_check_freshness`
|
|
257
|
+
| `OFW_ALLOW_MARK_READ=false` | Deployment-wide ceiling. No tool may stamp: `ofw_get_message` refuses, `ofw_check_freshness` / `ofw_status` ignore `allowMarkRead:true`, `ofw_sync_messages` ignores `fetchUnreadBodies:true`. A per-call argument cannot raise it. |
|
|
217
258
|
|
|
218
259
|
`OFW_FETCH_UNREAD_BODIES=true` flips `ofw_sync_messages` to fetch unread bodies by default (off unless set) — useful where read receipts are routine. It is capped by `OFW_ALLOW_MARK_READ`.
|
|
219
260
|
|