Ofw Mcp

io.github.chrischall由 GitHub 帳號 chrischall 發布v2.27.4更新於 Nov 7, 2026

OurFamilyWizard co-parenting for Claude — messages, calendar, expenses, and journal

概覽

AI 產生的概覽

把助理連接到 OurFamilyWizard 共同育兒帳號,可讀取訊息、行事曆、費用與日誌,並在受控條件下執行寫入。

可以做什麼
  • 讀取個人資料、儀表板計數、訊息資料夾與訊息,並把訊息同步到本機快取,附上新鮮度與完整性標記
  • 起草、儲存、刪除和傳送訊息,附件可從磁碟、內嵌 base64 或簽章上傳槽上傳
  • 列出行事曆事件與監護交接,把某段日期匯出成 ics 檔,並建立、更新或刪除事件
  • 檢視費用彙總、歷史與分類,登錄附收據 PDF 的費用,並記錄線下已完成的退款
  • 查詢草稿與訊息 id 的即時狀態,以及下載訊息附件
適用情境
適合已在用 OurFamilyWizard 的家長,讓助理彙整共同育兒訊息、查看共享監護行事曆,或準備草稿與費用紀錄,不必切換到網頁應用程式。
執行需求
以本機程序執行的 npm 套件,需要 Claude Desktop 與 Node.js 22.5 以上版本。需要有效的 OurFamilyWizard 帳號,透過 OFW_USERNAME 與 OFW_PASSWORD 登入,或借助已登入 ourfamilywizard.com 的 ContextMint Bridge 瀏覽器擴充功能。需要連線到 OurFamilyWizard 的網路。
安裝前請注意
OFW_PASSWORD 是真實帳號憑證,會存放在設定檔或 .env 檔中。第一次閱讀未讀訊息會留下共同撫養人可見且無法復原的首次檢視時間,可用 OFW_ALLOW_MARK_READ 與 OFW_READ_DAYS 限制。傳送訊息、登錄費用、共享行事曆變更與共享上傳會送達共同撫養人並進入可供法院檢視的紀錄;標記費用已退款無法復原。寫入逾時不代表失敗,重試前應先核對。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Ofw Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

OurFamilyWizard MCP

[CI] [npm] [license]

A Model Context Protocol server that connects Claude to OurFamilyWizard, giving you natural-language access to your co-parenting messages, calendar, expenses, and journal.

[!WARNING] AI-developed project. This codebase was entirely built and is actively maintained by Claude Sonnet 4.6. No human has audited the implementation. Review all code and tool permissions before use.

What you can do

Ask Claude things like:

  • "Show me my recent OFW messages"
  • "What's on the kids' calendar next week?"
  • "List recent expenses and tell me what I owe"
  • "Add a journal entry about today's pickup"
  • "Draft a reply to the last message from my co-parent"

Requirements

  • Claude Desktop
  • Node.js 22.5 or later (node:sqlite is the cache backend)
  • An active OurFamilyWizard account

Acknowledgement of Terms

By using this MCP server, you acknowledge and agree to the following:

1. This server accesses your own OurFamilyWizard account. Auth happens via your own credentials. It does not — and cannot — access your co-parent's account, your children's accounts, or anyone else's.

2. OurFamilyWizard's Terms govern your use of this server, just as they govern your direct use of OFW. There is no explicit anti-scraping clause; the governing language is broader:

Users may not obtain or attempt to obtain any materials or information through any means not intentionally made available.

And on credentials: "You are solely responsible for (1) maintaining the strict confidentiality of assigned Authentication Methods, (2) instructing any individual to whom the assigned Authentication Method is shared ('Authorized User') to not allow another person to use the Authentication Method." OFW does contemplate "Authorized Users" and third-party-enabled integrations — but the account holder remains responsible.

You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server.

3. Personal, family use only. This project is not affiliated with, endorsed by, sponsored by, or in partnership with OurFamilyWizard, LLC or its parent. It is a personal automation tool for the named account holder. Do not use it on behalf of a co-parent without their consent, do not share credentials with anyone, and do not use it to bulk-extract another family's data.

4. OFW is a court-of-record platform. Messages, expenses, calendar entries, and journal entries on OFW may be entered into legal proceedings — including custody, divorce, and parenting-plan-modification cases. Anything this server writes to OFW (drafts you save, events you create, expenses you log) will appear with the same legal weight as if you had typed it yourself. Do not let this MCP send a message, create an event, or log an expense that you have not read and approved. Review every write operation before confirming.

5. You accept full responsibility for any consequences — both technical (account warnings, suspension) and legal (anything OFW records about your account activity). The MCP author is not your attorney; if you're using OFW in connection with an active legal matter, talk to your actual attorney before automating anything.

This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede OurFamilyWizard's actual ToS.

Installation

1. Clone and build

bash
git clone https://github.com/chrischall/ofw-mcp.gitcd ofw-mcpnpm installnpm run build

2. Add to Claude Desktop

Edit your Claude Desktop config file:

  • Mac: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Add the ofw entry inside "mcpServers" (create the key if it doesn't exist):

json
{  "mcpServers": {    "ofw": {      "command": "node",      "args": ["/absolute/path/to/ofw-mcp/dist/index.js"],      "env": {        "OFW_USERNAME": "[email protected]",        "OFW_PASSWORD": "your-ofw-password"      }    }  }}

Replace /absolute/path/to/ofw-mcp with the actual path where you cloned the repo. On Mac, run pwd inside the cloned directory to get it.

3. Restart Claude Desktop

Quit completely (Cmd+Q on Mac, not just close the window) and relaunch.

4. Verify

Ask Claude: "What does my OFW dashboard look like?" — it should show your unread message count, upcoming events, and outstanding expenses.

Authentication

ofw-mcp tries three auth paths in order; whichever succeeds first is used. Existing setups keep working unchanged.

  1. Env-var credentials (legacy, recommended for Claude Desktop). Set OFW_USERNAME + OFW_PASSWORD and the server logs in via OFW's form endpoint. This is the path shown in the Claude Desktop config above.
  2. fetchproxy fallback (no env vars needed). When the credentials are absent, the server reads localStorage["auth"] once at startup from your already-signed-in ourfamilywizard.com tab via the ContextMint Bridge browser extension (built on fetchproxy). After that one read, all OFW API calls go directly from Node — the extension is not in the request hot path. Install ContextMint Bridge from its releases page (Chrome: download the Chrome zip, unzip it, and load it unpacked at chrome://extensions with Developer mode on; Safari is not available yet — it will ship inside the ContextMint app, which has no public download — so use Chrome for now), sign into OurFamilyWizard once, and the MCP just works. ContextMint Bridge is the fetchproxy browser extension under its new name, from the same maintainer — fetchproxy's own README (https://github.com/chrischall/fetchproxy#extension) points to it. Its source is public at https://github.com/nullnet-app/contextmint-bridge: build it yourself, or check a release zip against the .sha256 file published beside it (shasum -a 256 -c contextmint-bridge-chrome-<version>.zip.sha256). If you have multiple OFW accounts and want them to use separate caches, set OFW_CACHE_IDENTITY to a label per profile.
  3. Error. If neither path is available, the server tells you exactly which fix to apply. Set OFW_DISABLE_FETCHPROXY=1 to skip the fetchproxy fallback entirely (turns missing credentials into a hard error — useful in headless CI).

Credential options (env-var path)

Option A — env block in Claude Desktop config (shown above, recommended):

json
"env": {  "OFW_USERNAME": "[email protected]",  "OFW_PASSWORD": "your-ofw-password"}

Option B — .env file in the project directory:

bash
cp .env.example .env# edit .env and fill in your credentials

Environment variables always take priority over the .env file. You can also pass them directly on the command line:

bash
[email protected] OFW_PASSWORD=yourpass node dist/index.js

Available tools

Read-only tools run automatically. Writes that reach your co-parent or the court-visible record — Confirm (server) below — are confirmed by the server itself before anything is sent (see Server-side confirmation); the other writes rely on your MCP host's own approval prompt (Confirm). The Write mode column shows the minimum OFW_WRITE_MODE a tool needs to be available at all — see Write protection below.

ToolWhat it doesPermissionWrite mode
ofw_get_profileYour profile and co-parent infoAutoany
ofw_get_notificationsDashboard counts (unread messages, upcoming events, outstanding expenses). Updates your co-parent-visible last-seen status, so it is not marked read-onlyConfirmany
ofw_list_message_foldersFolders with unread counts — get folder IDs here before listing messagesAutoany
ofw_list_messagesMessages in a folderAutoany
ofw_get_messageFull content of a single messageAutoany
ofw_sync_messagesSync messages into the local cache and list every new message (newMessages). Unread inbox bodies are left unfetched, so a sync never marks anything read unless you ask for it; if it ever does, the response says so (warning, markedRead)Autoany
ofw_get_unread_sentSent messages a recipient hasn't read yet (from local cache)Autoany
ofw_check_freshnessCheap live check that the cache still matches OFW — per id, whether it is still a draft or was sent/deleted, without a full syncAutoany
ofw_statusOne live call for "where does everything stand?" — the full verified draft inventory, and the current state of any ids or draft keysAutoany
ofw_download_attachmentDownload a message attachment to disk, inline as extracted content / bytes, or (hosted) as a private expiring linkAutoany
ofw_send_messageSend a messageConfirm (server)all
ofw_list_draftsDraft messagesAutoany
ofw_save_draftCreate or update a draftConfirmdrafts
ofw_delete_draftDelete a draftConfirmdrafts
ofw_upload_attachmentUpload files to My Files — from a hosted session via signed upload slots (uploadSlots → PUT → blobKeys) or base64 inline; locally from the upload directory (path). Returns fileIds to attach via ofw_send_message/ofw_save_draft (which can also take inlineAttachments/blobKeys directly). Sharing (shareClass: SHARED) needs mode allAuto (PRIVATE) / Confirm (server) (SHARED)drafts
ofw_list_eventsCalendar events and custody handoffs in a date range; filter with types, read in a per-call timeZone, or get iCalendar text with format: "ics"Autoany
ofw_save_calendar_icsSave a date range as an .ics file — custody schedule included, which OFW's own export leaves out. A private, expiring download link on a hosted connector; a file in the attachments directory locallyAutoany
ofw_create_eventCreate a calendar eventConfirm (server) unless privateall (or drafts + OFW_CALENDAR_WRITES)
ofw_update_eventUpdate a calendar eventConfirm (server) if sharedall (or drafts + OFW_CALENDAR_WRITES)
ofw_delete_eventDelete a calendar eventConfirm (server) if sharedall (or drafts + OFW_CALENDAR_WRITES)
ofw_get_expense_totalsExpense summary totalsAutoany
ofw_list_expensesExpense historyAutoany
ofw_list_expense_categoriesExpense category ids and split metadataAutoany
ofw_upload_expense_pdfUpload a receipt PDF to My Files for an expense (SHARED — the co-parent sees it in My Files at once, even on a private expense)Confirm (server)all
ofw_create_expenseLog a new expense; supports private entries and one receipt PDFConfirm (server)all
ofw_mark_expense_reimbursedIrreversibly record full remaining external reimbursement; no transfer or partial payment; unavailable in upload-only modeConfirm (server)all
ofw_update_expenseChange an expense, e.g. publish a private one. Pass only the fields to change; it reads the expense first so the rest are keptConfirm (server)all
ofw_list_journal_entriesJournal entriesAutoany
ofw_create_journal_entryCreate a journal entryConfirmall

Uploading attachments

There are three ways to get a file to OurFamilyWizard, and which one works depends on where the server runs.

Hosted (claude.ai connector, mcp-host): signed upload slots. The server cannot see your machine, so path is refused there, with an explanation. Ask for a slot, PUT the file to it, then hand back its key:

jsonc
// 1. ofw_upload_attachment{ "uploadSlots": [{ "filename": "Sam_Think_Sheet_2026-11-03.jpg" }] }// → { "blobKeys": ["uploads/…"], "slots": [{ "uploadUrl": "https://…", "mimeType": "image/jpeg",//      "curl": "curl -sS --fail -X PUT -H 'Content-Type: image/jpeg' --data-binary @'<path to …>' '…'", "expiresAt": "…" }] }
// 2. run the slot's curl with the real file path
// 3. ofw_upload_attachment — or pass blobKeys straight to ofw_save_draft / ofw_send_message{ "blobKeys": ["uploads/…"], "label": "Sam Think Sheet 11-3-26" }// → { "uploaded": 1, "failed": 0, "fileIds": [12345], "results": [{ "ok": true, "fileId": 12345, "blobDeleted": true, … }] }

The PUT must send the slot's Content-Type (the URL is signed for it), and the URL works for 30 minutes. The server deletes each file from the upload store as soon as OurFamilyWizard has it. If the OFW upload fails, the file is kept, so the same key can be retried.

Any deployment, small files: inline base64. Every byte travels inside the tool call, and a hosted connector caps one request at about 10 MB of JSON (about 7 MB of files once base64-encoded). Use it for a document or a couple of small images, not a batch of phone photos:

jsonc
{ "inline": { "filename": "form.pdf", "contentBase64": "JVBERi0xLjcK…" } }// or an array of up to 10, with per-file results:{ "inline": [{ "filename": "a.jpg", "contentBase64": "…" }, { "filename": "b.heic", "contentBase64": "…", "mimeType": "image/heic" }] }

Local installs: path. A file inside the upload directory (OFW_UPLOAD_DIR, default ~/Downloads/ofw-mcp, created 0700 on first use). Paths outside it after resolving symlinks, hidden files, and files over 25 MiB are refused:

jsonc
{ "path": "Sam_Think_Sheet_2026-11-03.jpg" }

Every source has the same limits: up to 10 files per call, each non-empty and at most 25 MiB, with a bare filename. The MIME type comes from the extension unless you pass mimeType. Uploads are PRIVATE unless you pass shareClass: "SHARED", which goes one file at a time and is confirmed first.

To upload and attach in one step, give ofw_save_draft or ofw_send_message inlineAttachments (the same {filename, contentBase64, mimeType?} entries) or blobKeys. Every entry is validated before anything is uploaded.

Drafts don't hold attachments on OurFamilyWizard. OFW drops files from a saved draft, and its own web app only attaches them when the message is sent. So ofw_save_draft holds a draft's files on this server: they show under attachments.held, are kept across edits (removeFileIDs drops one), change the draft's revision, and are attached when you send the draft with ofw_send_message(draftId). You won't see them on the draft in the OFW web app. On send, files are attached as they are (PRIVATE files attach fine), and new files are uploaded PRIVATE only after you approve the send, with the approval bound to each new file's SHA-256. The response then checks the sent message and reports any file OFW left off.

On a hosted connector, ofw_download_attachment with inline:false (or link:true) returns a private download link that expires after linkMinutes (default 60), instead of a path on the server's disk. Inline images over 10 MiB fall back to a link too.

Data freshness (OFW_FRESHNESS_TTL_SECONDS)

Message and draft reads are served from the local cache, which means a result can look authoritative while being minutes or months out of date. The cache also cannot detect some changes on its own: editing a draft in the OFW web app bumps no timestamp at all, so "nothing changed" and "we didn't look" are indistinguishable unless the server says which happened.

So every read tool (ofw_list_messages, ofw_list_drafts, ofw_get_message, ofw_list_message_folders, ofw_sync_messages) returns a freshness block alongside its data:

json
"freshness": {  "source": "cache",  "asOf": "2026-07-20T12:40:00.000Z",  "ageSeconds": 5231,  "staleness": "unverified",  "lastServerSyncAt": "2026-07-20T13:59:00.000Z",  "syncComplete": false,  "historyComplete": true,  "warning": "Served from cache last verified 87 min ago; the last sync did not finish checking drafts. Re-read before asserting current state — call ofw_check_freshness for a cheap live confirmation, or ofw_sync_messages to refresh."}

staleness is fresh only when the data was fetched live in that call, or verified against OFW within the threshold by a sync that actually reached that folder. It degrades to unverified when it ages out or a sync skipped the folder, and stale when the folder has never been checked at all. Anything other than fresh always carries a human-readable warning stating the age and the reason. The bias is deliberate and one-directional: a false unverified costs one extra call, whereas a false fresh lets remembered state be narrated as present fact.

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.

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.

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.

Is it still what I think it is? (ofw_status)

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.

ofw_status is the call that should back any status summary:

json
{  "checkedAt": "2026-07-28T09:12:00.000Z",  "requested": [    { "id": 538279699, "state": "sent", "sentAt": "2026-07-27T23:31:09", "inSync": false },    { "id": 538086428, "state": "draft", "inSync": true }  ],  "complete": true}
  • 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".
  • With no arguments it returns the full draft inventory, verified against OFW first.
  • 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.

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.

Absence is never reported from a stale cache

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:

json
{  "result": "UNVERIFIED_EMPTY",  "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…",  "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.",  "complete": false}

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.

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.

Recording an external expense reimbursement

ofw_mark_expense_reimbursed records money already reimbursed outside OFW. It never transfers money, calls /payments, or initiates a bank action. OFW warns that marking reimbursed cannot be undone. The tool is registered only in OFW_WRITE_MODE=all, including expense-only mode, but never in OFW_EXPENSE_UPLOAD_ONLY=true; that restricted roster is unchanged.

Pass an expense id and expectedRemainingAmount in currency units, positive and exact to cents. For example, a synthetic expense totaling 96.99 with totalOwed=48.50 and leftToPay=48.50 needs { "expenseId": 81001, "expectedRemainingAmount": 48.50 } — not 96.99 and not a partial amount. The first call previews the title, author/payer user ids, split/category, children, privacy, total, owed amount, remaining balance and current status. Verify the parties and that the external reimbursement actually happened, then explicitly approve the preview before returning its confirmToken with the same arguments (or approve the supported client's confirmation prompt).

Only OPEN, APPROVED, or PARTIAL with subStatus=NONE and a positive balance are supported. Missing/malformed detail, unknown or pending status, inconsistent balances, id mismatch, or amount mismatch refuses without writing. A record already PAID with zero remaining balance returns ALREADY_PAID without a write, regardless of its payment method; this does not relabel an electronic payment.

Confirmation binds the entire detail record. A fresh detail GET immediately before the write rejects any drift, including receipt or metadata edits. The only mutation is PUT /pub/v2/expense/expenses/{id}/status with { "status": "MARKED_REIMBURSED" }. A subsequent detail GET must show PAID, subStatus=NONE, zero remaining balance, paymentType="Marked reimbursed", and unchanged fields outside the expected status/balance transition before success is reported. Known derived balance-display fields may change normally; numeric purchase/owed totals and other protected fields must remain unchanged. The public web application's status request contract and the resulting status transition were manually accepted against the real provider with one user-authorized record. Automated tests remain mock-only and contain synthetic records. Unexpected backend changes outside the supported transition still fail conservatively rather than claim verification.

These are best-effort preconditions, not an atomic conditional update: OFW exposes no conditional status-write contract here, so a concurrent edit after the last GET cannot be prevented. An ambiguous mutation failure (timeout/network/5xx) or failed/mismatched readback returns EXPENSE_REIMBURSEMENT_UNCONFIRMED with mayHaveLanded=true. Do not automatically replay or roll back. Inspect the expense in the Expenses log on ourfamilywizard.com and reconcile its status before any new attempt. Definitive OFW rejections remain errors. No live account or private family/financial data is used in the automated tests.

Server-side confirmation (MCP_CONFIRM_MODE)

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.

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.

VariableDefault
MCP_CONFIRM_MODEask-userWhat 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.
MCP_CONFIRM_ELICITATIONonoff 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.
MCP_CONFIRM_TTL_SECONDS600How long a token stays valid.
MCP_CONFIRM_SECRETrandom per processSigning 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.

Private events and PRIVATE uploads — which the co-parent never sees — are not gated. OFW_WRITE_MODE below stays the structural layer underneath: a tool your write mode excludes does not exist at all.

A write that times out is not a write that failed. If ofw_send_message, ofw_create_expense, ofw_create_event or ofw_create_journal_entry loses its connection or times out without a definitive answer from OFW, the result is SEND_UNCONFIRMED / EXPENSE_UNCONFIRMED / EVENT_UNCONFIRMED / JOURNAL_UNCONFIRMED: it may have landed. Check the matching list tool (or ourfamilywizard.com) before retrying, or the co-parent sees it twice.

Write protection (OFW_WRITE_MODE)

The host's "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.

OFW_WRITE_MODEWhat's available
noneRead/sync/search only. No write tools exist.
draftsAdds draft-level writes: ofw_save_draft, ofw_delete_draft, ofw_upload_attachment (PRIVATE only). Nothing that lands on the court-visible record — the AI prepares, only a human signed into the OFW web UI can send.
allEverything (the default — fully backward compatible).

Unrecognized values fail closed to none, with a warning on stderr — a typo never silently grants write access.

Expense-only deployments

For a dedicated reimbursement integration, you can also structurally remove unrelated OFW capabilities:

SettingRegistered surface
OFW_EXPENSE_ONLY=trueofw_healthcheck plus the expense tools only. Profile/dashboard, messages, calendar, and journal tools do not exist.
OFW_EXPENSE_UPLOAD_ONLY=trueStrictest mode: ofw_healthcheck, ofw_upload_expense_pdf, ofw_create_expense, and ofw_update_expense only. Expense totals/listing are removed too. This flag implies OFW_EXPENSE_ONLY.

These flags are registration-time restrictions, not prompt instructions. A host cannot call a tool that was never registered. Unrecognized non-empty flag values fail closed to the restricted state. OFW_WRITE_MODE still applies underneath; for the strict upload workflow set OFW_WRITE_MODE=all so expense creation is available.

Recommended reimbursement-only deployment:

env
OFW_EXPENSE_UPLOAD_ONLY=trueOFW_WRITE_MODE=allOFW_ALLOW_MARK_READ=false

Reading is a write, too (OFW_ALLOW_MARK_READ)

Fetching a message body for the first time marks it read on OurFamilyWizard and stamps a "First Viewed" timestamp your co-parent can see. That is part of the record and cannot be undone — and it happens as a side effect of an ordinary read, so OFW_WRITE_MODE does not govern it.

By default nothing changes: reads behave exactly as they always have. Two controls exist if you want them:

SettingEffect
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.
OFW_CONFIRM_MARK_READ=trueAsk first. Before ofw_get_message opens an unread inbox message it shows the subject, sender and sent time and asks you to confirm marking it read — a confirmation prompt where the client supports one, otherwise a preview plus a confirmToken that only a repeat call can redeem (MCP_CONFIRM_MODE, as for sends). An id that is not cached yet is refused with MARK_READ_CONFIRMATION_UNAVAILABLE (run ofw_sync_messages first — there is nothing to show you). ofw_sync_messages with fetchUnreadBodies (or OFW_FETCH_UNREAD_BODIES=true) asks too: the sync runs without marking anything read, then lists the unread inbox messages it would open (subject, sender, sent time; newest 25 per call) and opens them only once you confirm. The allowMarkRead probes on ofw_check_freshness / ofw_status have no message to show you, so they stay switched off. An unrecognized value fails closed to true.
OFW_ALLOW_MARK_READ=falseDeployment-wide ceiling. No tool may stamp: ofw_get_message refuses, ofw_check_freshness / ofw_status ignore allowMarkRead:true, ofw_sync_messages refuses fetchUnreadBodies:true and says so in its unreadBodies field. A per-call argument cannot raise it.

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 (and no-read days), and prompts first under OFW_CONFIRM_MARK_READ. OFW_ALLOW_MARK_READ=false wins over OFW_CONFIRM_MARK_READ — confirming cannot raise the ceiling.

Message-reading days (OFW_READ_DAYS / OFW_NO_READ_DAYS)

If you may only read OFW messages on certain days, list them. On every other day you can still see that a message arrived — sender, date, subject — but bodies and attachments are withheld and nothing is marked read on OurFamilyWizard.

env
OFW_READ_DAYS=mon,thu                       # allow list: read only on these daysOFW_NO_READ_DAYS=2026-12-24..2026-12-26     # deny list: never on these (wins over the allow list)

Tokens are weekday names (mon, Monday, …), dates (2026-12-25) or inclusive ranges (2026-12-24..2026-12-31), separated by commas or spaces, and "today" is decided in DISPLAY_TZ. Unset means every day is a reading day. A token that cannot be parsed fails closed (content withheld every day, warning on stderr). Like OFW_ALLOW_MARK_READ, no per-call argument can override it. Refusals come back as READING_BLOCKED with nextReadableDay. ofw_sync_messages downloads no message content on those days (only the list: sender, date, subject); the first sync on a reading day fills the bodies in, except for unread inbox messages, which still wait for you to open them. Drafts stay readable, and sending is not affected.

Calendar opt-in (OFW_CALENDAR_WRITES)

Calendar events sit between the two message tiers: they have no draft stage (a created event is immediately visible on the shared record), but unlike a sent message they are reversible — an event can be edited or deleted afterward. If you run in drafts mode but are comfortable with direct calendar writes, set OFW_CALENDAR_WRITES=true to additionally register ofw_create_event, ofw_update_event, and ofw_delete_event. The flag is redundant in all mode and never overrides none.

Egress allowlist

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.

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.

On a hosted connector the server also reaches mcp-host's own blob store (MCP_BLOB_BASE_URL, the gateway that serves the connector) with signed requests and no OFW credential. It stores .ics exports and download links, and reads (then deletes) files you upload through uploadSlots.

Upgrading

2.27.4 — ofw_sync_messages unreadInbox now reports the inbox as it was before the sync. It used to drop any message the same call opened. Now a message opened by fetchUnreadBodies stays in unreadInbox with bodyFetched: true, and is also listed in markedRead. If you read every unreadInbox entry as "still unread", skip the ones with bodyFetched: true (or use markedRead). Without fetchUnreadBodies nothing changes: a sync does not open unread inbox messages, so every entry is still unread.

Troubleshooting

"0 messages" — Claude may have read the notification counts rather than the actual messages. Ask explicitly: "List the messages in my OFW inbox" or "Use ofw_list_message_folders then ofw_list_messages".

"OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install ContextMint Bridge…" — neither auth path is configured. Either fill in the env block in your Claude Desktop config, or install ContextMint Bridge and sign into ourfamilywizard.com in your browser.

"fetchproxy fallback failed" / "ContextMint Bridge is down" — the env-var path wasn't configured and the extension couldn't be reached. Confirm ContextMint Bridge is installed, signed into OFW, and that it's running (open the extension popup). If you want to disable the fallback entirely, set OFW_DISABLE_FETCHPROXY=1.

"OFW login not attempted — this OurFamilyWizard email and password were already rejected" — OFW refused these credentials earlier in this session. OFW counts failed sign-ins against the account, so the server does not re-send a rejected password on every call. Update OFW_USERNAME / OFW_PASSWORD to your current login (or restart the server) and try again.

403 Forbidden — wrong credentials. Verify your username/password at ofw.ourfamilywizard.com.

Tools not appearing in Claude — go to Claude Desktop → Settings → Developer to see connected servers and any error output. Make sure you fully quit and relaunched after editing the config.

Can't find the config file on Mac — in Finder press Cmd+Shift+G and paste ~/Library/Application Support/Claude/.

Security

  • Credentials live only in your local config file or .env
  • They are passed to the server as environment variables and never logged
  • The server authenticates with OFW using the same login flow as the web app
  • Use a strong, unique OFW password
  • 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)

Local data, and how to remove it

The server keeps co-parenting data on this machine, readable only by your user account:

WhatWherePermissions
Message cache (message bodies, recipients, drafts, attachment records)~/.cache/ofw-mcp/<hash>.db (plus -wal/-shm), or OFW_CACHE_DIRdir 0700, files 0600
Downloaded attachments~/Downloads/ofw-mcp/, or OFW_ATTACHMENTS_DIRdirs the server creates 0700, files 0600
Session token$MCP_DATA_DIR/.ofw-mcp/session.json, or OFW_SESSION_FILE0600

The cache keeps everything it has synced until you remove it; nothing expires it. When you no longer need it — the account is closed, the case is over, or you are handing the machine on — quit your MCP host and delete it:

bash
rm -rf ~/.cache/ofw-mcp        # or your OFW_CACHE_DIRrm -rf ~/Downloads/ofw-mcp     # or your OFW_ATTACHMENTS_DIRrm -f  ~/.ofw-mcp/session.json # or your OFW_SESSION_FILE

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.

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.

Development

bash
npm test         # tsc typecheck, then the vitest suitenpm run build    # tsc → dist/, then esbuild bundle → dist/bundle.jsnpm run dev      # node --env-file=.env dist/index.js (requires built dist)

Main is protected. All changes land via PR — open with gh pr create --label <release-notes-label> and add ready-to-merge once you're satisfied with the auto-review feedback. See CLAUDE.md for the full PR + release flow.

Project structure

src/  index.ts          MCP server entry (McpServer + StdioServerTransport)  client.ts         OFW HTTP client with Bearer token + 401/429 retry  auth.ts           resolveAuth(): env-var creds → fetchproxy → error  auth-password.ts  Spring Security form login (legacy env-var path)  cache.ts          SQLite cache (messages, drafts, attachments, sync state)  sync.ts           Folder ID resolution + per-folder sync logic  config.ts         Cache dir, attachment dir, env parsing  tools/    _shared.ts      Recipient mapping, response helpers, path expansion    user.ts         ofw_get_profile, ofw_get_notifications    messages.ts     Folders, list, get, send, drafts, sync, attachments    calendar.ts     List, create, update, delete events    expenses.ts     Totals, list, create    journal.ts      List, create entriestests/              Mirrors src/; mocks OFWClient.request via vi.spyOn

Auth flow

Auth resolution lives in src/auth.ts. Three paths, in priority order:

  1. Env vars present → src/auth-password.ts does the legacy OFW Spring Security form login:
    1. GET /ofw/login.form — establishes a session cookie
    2. POST /ofw/login — submits credentials, returns { auth: "<token>" }
  2. Env vars absent (and OFW_DISABLE_FETCHPROXY unset) → @fetchproxy/bootstrap reads localStorage["auth"] + localStorage["tokenExpiry"] once from the user's signed-in ourfamilywizard.com tab, then closes the bridge.
  3. Nothing configured → throws with both fixes spelled out.

Either path returns a Bearer token to OFWClient, which then operates from Node with Authorization: Bearer <token> — fetchproxy is not in the request hot path. On 401 the client re-resolves auth and replays once. Tokens are cached for 6h (env-var path) or until tokenExpiry (fetchproxy path).

Also see the fetchproxy README for extension install instructions.

License

MIT

來源:README.md,提交 56bd441

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

21
  1. v2.27.4最新Nov 7, 2026
  2. v2.26.1Nov 4, 2026
  3. v2.24.0Nov 3, 2026
  4. v2.23.0Oct 29, 2026
  5. v2.21.0Oct 26, 2026
  6. v2.20.6Oct 20, 2026
  7. v2.20.5Oct 16, 2026
  8. v2.20.4Oct 10, 2026
  9. v2.20.3Oct 7, 2026
  10. v2.20.2Oct 5, 2026
  11. v2.20.1Oct 3, 2026
  12. v2.20.0Sep 29, 2026
  13. v2.19.5Sep 28, 2026
  14. v2.19.4Sep 25, 2026
  15. v2.19.3Sep 23, 2026
  16. v2.19.2Sep 23, 2026
  17. v2.19.1Sep 21, 2026
  18. v2.19.0Sep 20, 2026
  19. v2.18.0Sep 20, 2026
  20. v2.17.0Sep 17, 2026
  21. v2.16.3Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分