ofw-mcp 2.4.4 → 2.5.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.
@@ -0,0 +1,252 @@
1
+ # OurFamilyWizard requests for fpx + curl
2
+
3
+ Base URL for every call: `https://ofw.ourfamilywizard.com`. Every request
4
+ carries these three headers (from `src/protocol.ts` / `src/client.ts`):
5
+
6
+ ```sh
7
+ AUTH_HEADERS=(-H "Authorization: Bearer $TOKEN" -H 'ofw-client: WebApplication' -H 'ofw-version: 1.0.0')
8
+ ```
9
+
10
+ `$TOKEN` comes from the one-time capture in `SKILL.md`. All paths, params,
11
+ and bodies below are transcribed from `src/tools/*.ts`, `src/sync.ts`, and
12
+ `src/tools/_shared.ts` — the exact shapes `ofw-mcp` sends.
13
+
14
+ ---
15
+
16
+ ## 1. Profile & dashboard
17
+
18
+ **Current user + co-parent profile:**
19
+
20
+ ```sh
21
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v2/profiles' "${AUTH_HEADERS[@]}" | jq .
22
+ ```
23
+
24
+ **Dashboard summary (unread count, upcoming events, outstanding expenses).
25
+ Note: this call updates your last-seen status on OFW, same as opening the
26
+ web app's dashboard:**
27
+
28
+ ```sh
29
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v1/users/useraccountstatus' "${AUTH_HEADERS[@]}" | jq .
30
+ ```
31
+
32
+ ## 2. Messages — folders, list, detail
33
+
34
+ **Folder IDs + unread counts** (needed before listing by folder):
35
+
36
+ ```sh
37
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v1/messageFolders?includeFolderCounts=true' "${AUTH_HEADERS[@]}" \
38
+ | jq '.systemFolders[] | {id, folderType}'
39
+ # folderType is one of INBOX / SENT_MESSAGES / DRAFTS
40
+ ```
41
+
42
+ **List messages in a folder** (date-desc, 50/page is what the MCP's sync
43
+ uses; unread inbox items carry `showNeverViewed: true` — the reliable
44
+ unread signal, per CLAUDE.md):
45
+
46
+ ```sh
47
+ FOLDER_ID=<id from above>
48
+ curl -s "https://ofw.ourfamilywizard.com/pub/v3/messages?folders=${FOLDER_ID}&page=1&size=50&sort=date&sortDirection=desc" \
49
+ "${AUTH_HEADERS[@]}" \
50
+ | jq '.data[] | {id, subject, sentAt: .date.dateTime, from: .from.name, showNeverViewed}'
51
+ ```
52
+
53
+ **Message/draft detail by id** (GETting an unread inbox message marks it
54
+ read on OFW):
55
+
56
+ ```sh
57
+ curl -s "https://ofw.ourfamilywizard.com/pub/v3/messages/${ID}" "${AUTH_HEADERS[@]}" \
58
+ | jq '{id, subject, body, sentAt: .date.dateTime, from: .from.name, files, recipients: [.recipients[] | {id: .user.id, name: .user.name, viewedAt: .viewed.dateTime}]}'
59
+ ```
60
+
61
+ ## 3. Send a message / save a draft (write — confirm-by-re-GET)
62
+
63
+ Both send and save-draft POST the same shape to `/pub/v3/messages`; only
64
+ `draft` (bool) differs. **Never pass `messageId`/an existing id in this
65
+ POST** — OFW's update-in-place endpoint silently no-ops on repeat edits
66
+ while echoing success. To "replace" a draft: POST a fresh one, confirm it
67
+ landed, then bulk-delete the old id (§4).
68
+
69
+ ```sh
70
+ BODY=$(jq -n \
71
+ --arg subject 'Pickup time change' \
72
+ --arg body 'Can we move Friday pickup to 5pm instead of 4?' \
73
+ --argjson recipientIds '[12345]' \
74
+ --argjson myFileIDs '[]' \
75
+ --arg draft false \
76
+ --arg includeOriginal false \
77
+ --argjson replyToId null \
78
+ '{subject:$subject, body:$body, recipientIds:$recipientIds,
79
+ attachments:{myFileIDs:$myFileIDs}, draft:($draft=="true"),
80
+ includeOriginal:($includeOriginal=="true"), replyToId:$replyToId}')
81
+
82
+ RESP=$(curl -s -X POST 'https://ofw.ourfamilywizard.com/pub/v3/messages' \
83
+ "${AUTH_HEADERS[@]}" -H 'Content-Type: application/json' --data "$BODY")
84
+
85
+ NEW_ID=$(jq -r '.id // .entityId // empty' <<<"$RESP")
86
+ [ -n "$NEW_ID" ] || { echo "SEND UNCONFIRMED: no id in response: $RESP" >&2; exit 1; }
87
+
88
+ # Re-GET immediately — the only honest way to confirm the write landed.
89
+ DETAIL=$(curl -s "https://ofw.ourfamilywizard.com/pub/v3/messages/${NEW_ID}" "${AUTH_HEADERS[@]}")
90
+ jq -e --arg s 'Pickup time change' --arg b 'Can we move Friday pickup' \
91
+ '(.subject // "" | contains($s)) and (.body // "" | contains($b))' <<<"$DETAIL" >/dev/null \
92
+ && echo "confirmed id=$NEW_ID" || echo "WARNING: re-fetched body/subject does not contain what was sent — verify on ourfamilywizard.com" >&2
93
+ ```
94
+
95
+ For a **draft**, set `draft:true`; `subject`/`body` are the only required
96
+ fields (`recipientIds` may be `[]`).
97
+
98
+ To **reply**, set `replyToId` to the parent message id and
99
+ `includeOriginal:true` (OFW appends the original message to the body
100
+ server-side — that's why containment, not equality, is the right check
101
+ above).
102
+
103
+ ## 4. Delete messages/drafts (bulk, multipart)
104
+
105
+ Same endpoint deletes both sent-message ids and draft ids — pass whichever
106
+ you mean:
107
+
108
+ ```sh
109
+ curl -s -X DELETE 'https://ofw.ourfamilywizard.com/pub/v1/messages' \
110
+ "${AUTH_HEADERS[@]}" \
111
+ -F 'messageIds=111' -F 'messageIds=222' # repeat -F per id
112
+ ```
113
+
114
+ ## 5. Attachments
115
+
116
+ **Upload a file to "My Files"** (multipart; `shareClass` is `PRIVATE` or
117
+ `SHARED`; matches the web UI's upload request in `src/tools/messages.ts`):
118
+
119
+ ```sh
120
+ curl -s -X POST 'https://ofw.ourfamilywizard.com/pub/v3/myfiles/multipart' \
121
+ "${AUTH_HEADERS[@]}" \
122
+ -F "file=@/path/to/file.pdf;type=application/pdf" \
123
+ -F 'source=message' \
124
+ -F 'description=file.pdf' \
125
+ -F 'label=file.pdf' \
126
+ -F 'fileName=file.pdf' \
127
+ -F 'shareClass=PRIVATE' \
128
+ | jq '{fileId, fileName, fileType, sizeInBytes}'
129
+ ```
130
+
131
+ The response's `fileId` is what you pass as `myFileIDs` in §3's POST body
132
+ (`attachments.myFileIDs`) to attach it to a message/draft.
133
+
134
+ **Attachment metadata:**
135
+
136
+ ```sh
137
+ curl -s "https://ofw.ourfamilywizard.com/pub/v1/myfiles/${FILE_ID}" "${AUTH_HEADERS[@]}" \
138
+ | jq '{fileId, fileName, fileType, fileSize, label}'
139
+ ```
140
+
141
+ **Download attachment bytes** (binary — write straight to a file, don't
142
+ pipe through `jq`):
143
+
144
+ ```sh
145
+ curl -s "https://ofw.ourfamilywizard.com/pub/v1/myfiles/${FILE_ID}/data" \
146
+ "${AUTH_HEADERS[@]}" -o "./${FILE_ID}-download"
147
+ ```
148
+
149
+ ## 6. Calendar
150
+
151
+ **List events** (`basic` or `detailed`; dates are `YYYY-MM-DD`):
152
+
153
+ ```sh
154
+ curl -s "https://ofw.ourfamilywizard.com/pub/v1/calendar/basic?startDate=2026-07-01&endDate=2026-07-31" \
155
+ "${AUTH_HEADERS[@]}" | jq .
156
+ # swap "basic" for "detailed" for full event details
157
+ ```
158
+
159
+ **Create an event** (write — court-visible; `eventFor` is
160
+ `neither|parent1|parent2`):
161
+
162
+ ```sh
163
+ BODY=$(jq -n '{
164
+ title: "Soccer practice",
165
+ startDate: "2026-07-20T16:00:00",
166
+ endDate: "2026-07-20T17:30:00",
167
+ allDay: false,
168
+ location: "Community field",
169
+ reminder: "1 hour before",
170
+ privateEvent: false,
171
+ eventFor: "neither",
172
+ children: [67890]
173
+ }')
174
+ curl -s -X POST 'https://ofw.ourfamilywizard.com/pub/v1/calendar/events' \
175
+ "${AUTH_HEADERS[@]}" -H 'Content-Type: application/json' --data "$BODY" | jq .
176
+ ```
177
+
178
+ **Update an event** (send only the fields you're changing):
179
+
180
+ ```sh
181
+ curl -s -X PUT "https://ofw.ourfamilywizard.com/pub/v1/calendar/events/${EVENT_ID}" \
182
+ "${AUTH_HEADERS[@]}" -H 'Content-Type: application/json' \
183
+ --data '{"title":"Soccer practice (moved)","startDate":"2026-07-20T17:00:00"}' | jq .
184
+ ```
185
+
186
+ **Delete an event:**
187
+
188
+ ```sh
189
+ curl -s -X DELETE "https://ofw.ourfamilywizard.com/pub/v1/calendar/events/${EVENT_ID}" "${AUTH_HEADERS[@]}"
190
+ ```
191
+
192
+ ## 7. Expenses
193
+
194
+ **Totals (owed/paid):**
195
+
196
+ ```sh
197
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v2/expense/expenses/totals' "${AUTH_HEADERS[@]}" | jq .
198
+ ```
199
+
200
+ **List expenses** (offset-based, 0-indexed `start`):
201
+
202
+ ```sh
203
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v2/expense/expenses?start=0&max=20' "${AUTH_HEADERS[@]}" | jq .
204
+ ```
205
+
206
+ **Create an expense** (write):
207
+
208
+ ```sh
209
+ curl -s -X POST 'https://ofw.ourfamilywizard.com/pub/v2/expense/expenses' \
210
+ "${AUTH_HEADERS[@]}" -H 'Content-Type: application/json' \
211
+ --data '{"amount": 45.00, "description": "Cleats for soccer"}' | jq .
212
+ ```
213
+
214
+ ## 8. Journal
215
+
216
+ **List entries** (offset-based, but **1-indexed** `start` — unlike
217
+ expenses):
218
+
219
+ ```sh
220
+ curl -s 'https://ofw.ourfamilywizard.com/pub/v1/journals?start=1&max=10' "${AUTH_HEADERS[@]}" | jq .
221
+ ```
222
+
223
+ **Create an entry** (write — journal entries are a permanent court record):
224
+
225
+ ```sh
226
+ curl -s -X POST 'https://ofw.ourfamilywizard.com/pub/v1/journals' \
227
+ "${AUTH_HEADERS[@]}" -H 'Content-Type: application/json' \
228
+ --data '{"title": "Missed pickup", "body": "Co-parent arrived 45 min late without notice."}' | jq .
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Auth-error / retry recipe (wrap any of the above)
234
+
235
+ ```sh
236
+ RESP_FILE=$(mktemp /tmp/ofw-resp.XXXXXX.json)
237
+ trap 'rm -f "$RESP_FILE"' EXIT
238
+
239
+ call() { curl -s -o "$RESP_FILE" -w '%{http_code}' "$@" "${AUTH_HEADERS[@]}"; }
240
+
241
+ STATUS=$(call 'https://ofw.ourfamilywizard.com/pub/v2/profiles')
242
+ if [ "$STATUS" = "429" ]; then
243
+ sleep 2
244
+ STATUS=$(call 'https://ofw.ourfamilywizard.com/pub/v2/profiles')
245
+ fi
246
+ if [ "$STATUS" = "401" ]; then
247
+ echo "token expired — reload/sign in on the ourfamilywizard.com tab, then re-run the fpx local-storage capture" >&2
248
+ exit 1
249
+ fi
250
+ [ "$STATUS" -lt 300 ] || { echo "OFW API error: $STATUS $(cat "$RESP_FILE")" >&2; exit 1; }
251
+ jq . "$RESP_FILE"
252
+ ```