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/mint.yaml ADDED
@@ -0,0 +1,143 @@
1
+ version: 1
2
+ name: OurFamilyWizard
3
+ slug: ofw
4
+ summary: >-
5
+ OurFamilyWizard co-parenting tools for Claude — messages, calendar,
6
+ expenses, and journal
7
+ #
8
+ # Hosting note (a comment, not user-facing summary text): this is a
9
+ # BROWSER-BRIDGE MCP — it reaches its site through the user's signed-in
10
+ # tab via the fetchproxy bridge. A bridged registration also needs runtime
11
+ # `fly-shared`, `bridge: true` and a `bridgePortEnv`, which are registration
12
+ # fields this manifest has no schema for (set them over the control API).
13
+ # `state.dataDir` below is required for `bridge`.
14
+ env:
15
+ - name: OFW_USERNAME
16
+ required: false
17
+ help: >-
18
+ Your OurFamilyWizard login email address. Optional — if omitted, the
19
+ server falls back to the fetchproxy browser extension (requires being
20
+ signed in to ourfamilywizard.com).
21
+ - name: OFW_PASSWORD
22
+ secret: true
23
+ required: false
24
+ help: >-
25
+ Your OurFamilyWizard password. Optional — see OFW_USERNAME.
26
+ - name: OFW_WRITE_MODE
27
+ required: false
28
+ help: >-
29
+ Write-tool gate: "none" registers no write tools; "drafts" registers
30
+ draft-level writes only (save/delete drafts, upload attachments); "all"
31
+ registers everything (default). Unrecognized values fail closed to "none".
32
+ - name: OFW_CALENDAR_WRITES
33
+ required: false
34
+ help: >-
35
+ Set to "true" to register calendar write tools (create/update/delete
36
+ event) in "drafts" write mode. Events have no draft stage but are
37
+ reversible. Never overrides "none".
38
+ - name: OFW_ALLOW_MARK_READ
39
+ required: false
40
+ help: >-
41
+ Default "true". Set "false" to forbid any tool from marking a message read
42
+ on OurFamilyWizard - reading a body for the first time stamps a
43
+ co-parent-visible "First Viewed" time that cannot be undone. A ceiling: no
44
+ per-call argument can raise it.
45
+ - name: OFW_FETCH_UNREAD_BODIES
46
+ required: false
47
+ help: >-
48
+ Default "false". Whether ofw_sync_messages fetches unread inbox bodies by
49
+ default (each fetch stamps a First Viewed time). Capped by
50
+ OFW_ALLOW_MARK_READ.
51
+ - name: OFW_INLINE_ATTACHMENTS
52
+ required: false
53
+ help: >-
54
+ When on, ofw_download_attachment returns bytes inline as MCP content
55
+ (images render directly; other files come back as embedded resources)
56
+ instead of writing to disk. Recommended for sandboxed hosts like Claude
57
+ Desktop where the model cannot read files written to ~/Downloads. Callers
58
+ can still override per-call via the tool's `inline` argument.
59
+ - name: OFW_ATTACHMENTS_DIR
60
+ required: false
61
+ help: >-
62
+ Directory where ofw_download_attachment writes files when not returning
63
+ inline. Defaults to ~/Downloads/ofw-mcp/. Pick a directory that is
64
+ readable by your MCP host.
65
+ - name: DISPLAY_TZ
66
+ required: false
67
+ help: >-
68
+ IANA time zone (e.g. America/New_York) that every rendered *…Display*
69
+ field uses, and the zone a naive OFW timestamp is assumed to be in. Must
70
+ match the OFW account's own zone. Never a fixed offset — that would be an
71
+ hour wrong for half the year.
72
+ - name: OFW_DEBUG_LOG
73
+ required: false
74
+ help: >-
75
+ Set to 1 to write verbose request/response diagnostics to stderr. Off by
76
+ default; useful when a read is failing and you need to see the upstream
77
+ exchange.
78
+ - name: OFW_AUTO_REFRESH
79
+ required: false
80
+ help: >-
81
+ Set to true and read tools sync the backing folders themselves and answer
82
+ from the refreshed cache, instead of refusing a stale read. The refusal
83
+ still fires if the refresh does not make the read verifiable.
84
+ - name: OFW_CACHE_DIR
85
+ required: false
86
+ help: >-
87
+ Directory for persisted session/cache state. Relates to state.dataDir
88
+ below; leave unset to use the default under $HOME.
89
+ - name: OFW_CACHE_IDENTITY
90
+ required: false
91
+ help: >-
92
+ Labels the per-user SQLite cache file. Useful when authenticating via the
93
+ browser bridge, where OFW_USERNAME is not set and the cache would
94
+ otherwise fall back to a shared default name.
95
+ - name: OFW_DISABLE_FETCHPROXY
96
+ required: false
97
+ help: >-
98
+ Set to 1 to disable the browser-bridge fallback, making missing
99
+ credentials a hard error. Recommended for a hosted registration, which has
100
+ no browser to fall back to.
101
+ - name: OFW_FRESHNESS_TTL_SECONDS
102
+ required: false
103
+ help: >-
104
+ Age in seconds past which a read result's freshness block downgrades to
105
+ "unverified" and grows a warning (default 300). Unset means never
106
+ downgrade.
107
+ - name: OFW_REQUEST_TIMEOUT_MS
108
+ required: false
109
+ help: >-
110
+ Per-request timeout in milliseconds. Raise it if calls time out on slow
111
+ upstream responses.
112
+ - name: OFW_SYNC_MAX_REQUESTS
113
+ required: false
114
+ help: >-
115
+ Caps the OFW requests one ofw_sync_messages call may make before pausing;
116
+ the next call resumes where it left off. Set a positive integer when
117
+ hosted, where a per-call request cap can otherwise truncate a deep
118
+ backfill.
119
+ - name: OFW_SESSION_CACHE
120
+ required: false
121
+ help: >-
122
+ Set to false to disable the on-disk session cache and re-authenticate on
123
+ every process start. Defaults to enabled.
124
+ - name: OFW_SESSION_FILE
125
+ required: false
126
+ help: >-
127
+ Absolute path for the session cache file. Defaults to
128
+ $MCP_DATA_DIR/.ofw-mcp/session.json.
129
+ state:
130
+ dataDir: true
131
+ reason: >-
132
+ Two things live here. The fetchproxy identity is at
133
+ $HOME/.fetchproxy/identity/<name>.json and the pair code derives from it —
134
+ without a persistent $HOME every cold start mints a fresh identity and
135
+ re-prompts pairing in the browser, and the API refuses bridge without it.
136
+ The session token is cached at $MCP_DATA_DIR/.ofw-mcp/session.json (0600);
137
+ OFW has no refresh grant, so without it every cold start re-runs the full
138
+ login for a token that was still good for up to six hours.
139
+ egress:
140
+ allow:
141
+ # Only hosts the SERVER process actually fetches. Hosts that appear
142
+ # solely in a URL this server BUILDS and returns are excluded.
143
+ - ofw.ourfamilywizard.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.10.2",
3
+ "version": "2.12.0",
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)",
@@ -21,18 +21,20 @@
21
21
  ".claude-plugin",
22
22
  "skills",
23
23
  ".mcp.json",
24
- "server.json"
24
+ "server.json",
25
+ "mint.yaml"
25
26
  ],
26
27
  "scripts": {
27
28
  "build": "tsc && npm run bundle",
28
29
  "bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from \"module\"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js",
29
30
  "dev": "node --env-file=.env dist/index.js",
30
- "test": "vitest run",
31
- "test:coverage": "vitest run --coverage",
32
- "test:watch": "vitest"
31
+ "test": "npm run typecheck && vitest run",
32
+ "test:coverage": "npm run typecheck && vitest run --coverage",
33
+ "test:watch": "vitest",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit"
33
35
  },
34
36
  "dependencies": {
35
- "@chrischall/mcp-utils": "^0.14.0",
37
+ "@chrischall/mcp-utils": "^0.18.0",
36
38
  "@fetchproxy/bootstrap": "^2.0.0",
37
39
  "@modelcontextprotocol/sdk": "^1.29.0",
38
40
  "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.10.2",
9
+ "version": "2.12.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.10.2",
14
+ "version": "2.12.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -52,6 +52,18 @@
52
52
  "description": "Default \"false\". Whether ofw_sync_messages fetches unread inbox bodies by default (each fetch stamps a First Viewed time). Capped by OFW_ALLOW_MARK_READ.",
53
53
  "isRequired": false,
54
54
  "format": "string"
55
+ },
56
+ {
57
+ "name": "OFW_SESSION_CACHE",
58
+ "description": "Set to false to disable the on-disk session cache and re-authenticate on every process start. Defaults to enabled.",
59
+ "isRequired": false,
60
+ "format": "string"
61
+ },
62
+ {
63
+ "name": "OFW_SESSION_FILE",
64
+ "description": "Absolute path for the session cache file. Defaults to $MCP_DATA_DIR/.ofw-mcp/session.json.",
65
+ "isRequired": false,
66
+ "format": "string"
55
67
  }
56
68
  ]
57
69
  }
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ofw-mcp
2
+ name: ofw
3
3
  description: This skill should be used when the user asks about OurFamilyWizard (OFW) co-parenting data. Triggers on phrases like "check OFW", "OurFamilyWizard inbox", "OFW messages", "OFW calendar", "OFW expenses", "what did my co-parent say", "log an expense in OFW", "OFW journal", or any request involving co-parenting messages, calendar events, shared expenses, or journal entries.
4
4
  ---
5
5
 
@@ -92,11 +92,11 @@ Always pass `--config ~/.mcporter/mcporter.json` unless a local `config/mcporter
92
92
  |------|-------|
93
93
  | `ofw_sync_messages(folders?, deep?, fetchUnreadBodies?)` | Sync OFW → local cache. **Call first if the cache might be stale.** Returns unread inbox hints (bodies not fetched, to avoid mark-as-read). |
94
94
  | `ofw_list_message_folders` | List OFW folders with unread counts. Most reads use the cache; this is mainly for folder IDs and live unread counts. |
95
- | `ofw_list_messages(folderId?, since?, until?, q?, page?, size?, autoRefresh?)` | Cache-backed list. Supports folder ("inbox"/"sent"/"both"), date range, and substring search. Returns `complete` for the RESULT SET. An **empty** result from a non-fresh cache is refused (`UNVERIFIED_EMPTY`) — pass `autoRefresh:true` to sync and answer instead. |
95
+ | `ofw_list_messages(folderId?, since?, until?, q?, sort?, page?, size?, autoRefresh?)` | Cache-backed list. Supports folder ("inbox"/"sent"/"both"), date range, and substring search. `sort:"oldest"` starts at the old end of a range instead of paging to it (default `"newest"`). Returns `complete` for the RESULT SET plus **`nextPage`** (null when done) — and the paging keys come FIRST in the JSON, before `messages`. `returned` carries the record count as a scalar, beside `total`. An **empty** result from a non-fresh cache is refused (`UNVERIFIED_EMPTY`) — pass `autoRefresh:true` to sync and answer instead. |
96
96
  | `ofw_get_message(messageId, allowMarkRead?)` | Read a message OR draft body. Cache-first. Ids in the drafts cache return `folder: "drafts"`. ⚠️ Falls through to OFW for unread inbox messages, which marks them read AND stamps a "First Viewed" time the co-parent can see — irreversible. Pass `allowMarkRead:false` to refuse that fetch instead; cached, sent and already-read messages are unaffected. |
97
97
  | `ofw_send_message(draftId?, subject?, body?, recipientIds?, replyToId?, expectedRevision?, deleteDraftOnSuccess?, myFileIDs?, force?)` | Send a message — **the one irreversible operation**. Preferred path: pass `draftId` (+ `expectedRevision`) to send an existing draft **as it exists on the server** — the tool re-reads it from OFW first and refuses if it changed since you read it (or was already sent/deleted); `subject`/`body` become optional overrides. `recipientIds` is usually still required: OFW does not store recipients on drafts. After a **confirmed** send the draft is auto-deleted (`deleteDraftOnSuccess:false` to keep it); on any failure or ambiguity it is retained and the response says why (`draftRetained`). Response leads with `sentMessageId`, `draftKey`, `threaded`, `draftDeleted`. Compose from scratch by passing `subject`/`body`/`recipientIds` with no `draftId`. |
98
- | `ofw_get_unread_sent(page?, size?, autoRefresh?)` | Sent messages your co-parent hasn't read yet (from cache). Reports `scanned`/`total`/`complete`; an empty sent cache that is not fresh is refused rather than reported as "nothing sent". |
99
- | `ofw_list_drafts(page?, size?, verify?, autoRefresh?)` | List saved drafts, **auto-verified**: when the cache is not verified-fresh a cheap drafts sync runs first (default `verify:true`), so one call answers server-confirmed. `verify:false` serves straight from cache. Each draft carries `serverConfirmed`, `revision` and `draftKey`. Returns `complete` — **check it before saying "you have N drafts"**. See [Freshness](#freshness). |
98
+ | `ofw_get_unread_sent(page?, size?, autoRefresh?)` | Sent messages your co-parent hasn't read yet (from cache). Leads with `complete`/`hasMore`/`nextPage`, then `scanned`/`total`, then `unread`; an empty sent cache that is not fresh is refused rather than reported as "nothing sent". |
99
+ | `ofw_list_drafts(page?, size?, verify?, autoRefresh?)` | Leads with `complete`/`hasMore`/`nextPage`; `drafts` comes last. List saved drafts, **auto-verified**: when the cache is not verified-fresh a cheap drafts sync runs first (default `verify:true`), so one call answers server-confirmed. `verify:false` serves straight from cache. Each draft carries `serverConfirmed`, `revision` and `draftKey`. Returns `complete` — **check it before saying "you have N drafts"**. See [Freshness](#freshness). |
100
100
  | `ofw_save_draft(subject, body, recipientIds?, messageId?, replyToId?, myFileIDs?, expectedRevision?, force?)` | Create a new draft. Pass `messageId` to **replace** an existing draft: the tool creates a fresh draft and deletes the old one (OFW's update-in-place endpoint silently no-ops). The returned `id` is the NEW id; the response leads with `draftKey`, which stays the same across every edit — **track that, not the id**. Note: OFW does **not** store recipients on drafts — `recipientIds` are accepted but come back empty (a one-line NOTE says so; supply them at send time instead). Threading warnings fire only on genuine drops — a draft echoing `inReplyTo`/`showContext` IS threaded. |
101
101
  | `ofw_delete_draft(messageId)` | Delete a draft. |
102
102
  | `ofw_upload_attachment(path, shareClass?, label?, description?)` | Upload a local file to My Files; returns a fileId to pass into `myFileIDs`. |
@@ -133,6 +133,7 @@ Message and draft reads come from a local cache, so **a result can be stale with
133
133
  |---|---|
134
134
  | How old is this data? | `freshness` — `staleness` (`fresh`/`unverified`/`stale`), `asOf`, `ageSeconds`, a quotable `warning` |
135
135
  | Is this the WHOLE answer? | `complete` on `ofw_list_messages` / `ofw_list_drafts` / `ofw_get_unread_sent` / `ofw_status` |
136
+ | If not, how do I get the rest? | `nextPage` (message tools) or `nextStart` (`ofw_list_expenses` / `ofw_list_journal_entries`) — null means there is no more |
136
137
  | Is this entity still what I think it is? | `state` from `ofw_status` / `ofw_check_freshness` |
137
138
 
138
139
  Rules: