ofw-mcp 2.10.1 → 2.10.2

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.10.1"
9
+ "version": "2.10.2"
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.10.1",
17
+ "version": "2.10.2",
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.10.1",
4
+ "version": "2.10.2",
5
5
  "description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall"
package/README.md CHANGED
@@ -124,17 +124,6 @@ Environment variables always take priority over the `.env` file. You can also pa
124
124
  OFW_USERNAME=you@example.com OFW_PASSWORD=yourpass node dist/index.js
125
125
  ```
126
126
 
127
- ## Hosted connector (Cloudflare Worker)
128
-
129
- Instead of running `ofw-mcp` locally, you can add it to [claude.ai](https://claude.ai) as a **remote MCP connector** — a hosted Cloudflare Worker you reach from Settings → Connectors on Claude web, desktop, or mobile (connectors sync across all three). The same tool registrars back both targets, so the tools and behaviour are identical to the local stdio install; the Worker just wraps them with [`@chrischall/mcp-connector`](https://www.npmjs.com/package/@chrischall/mcp-connector) (the shared OAuth + streamable-HTTP harness) and a per-user [Durable Object](src/cache/durable.ts) cache in place of the local SQLite file.
130
-
131
- - **How you connect.** Each person you share the connector URL with logs in through the connector's own OAuth page with their **own** OurFamilyWizard email and password. Those credentials are stored (encrypted at rest) per user because OFW bearer tokens expire after ~6h with no refresh token, so the connector must be able to re-login on its own. One user can never see another's account or cache.
132
- - **Attachments are inline-only.** The Worker has no local filesystem, so `ofw_download_attachment` always returns content as MCP content blocks (`OFW_INLINE_ATTACHMENTS=true`) rather than writing to disk. Spreadsheets, PDFs and Office documents come back as extracted text/CSV, so they are readable even though the host cannot render the file itself.
133
- - **Write mode defaults to `all`.** The hosted connector registers every tool by default, configurable per deployment via `OFW_WRITE_MODE` / `OFW_CALENDAR_WRITES` in `wrangler.jsonc` — see [Write protection](#write-protection-ofw_write_mode).
134
- - **Message sync is bounded and resumable.** To stay under Cloudflare's per-request subrequest cap, `ofw_sync_messages` on the hosted connector caps how many OFW requests one call makes (`OFW_SYNC_MAX_REQUESTS` in `wrangler.jsonc`, default `40`) and resumes across calls, so a large mailbox backfills over multiple `ofw_sync_messages` calls rather than one; the local stdio server is unbounded. See [`docs/DEPLOY-CONNECTOR.md`](docs/DEPLOY-CONNECTOR.md#sync--the-subrequest-limit).
135
-
136
- Standing this up requires a Cloudflare account and is a one-time setup for whoever hosts it; after that the `deploy-connector` job in `release-please.yml` deploys each release automatically (and **Actions → deploy-connector → Run workflow** deploys any ref on demand) — see [`docs/DEPLOY-CONNECTOR.md`](docs/DEPLOY-CONNECTOR.md) for the full runbook. `wrangler.jsonc` serves the Worker at a custom domain (`https://connector.ofw.nullnet.app/mcp`) plus the account's `*.workers.dev` URL; whoever hosts it uses their own domain. The local stdio / `.mcpb` install above remains the desktop-only alternative if you'd rather run it against just your own account.
137
-
138
127
  ## Available tools
139
128
 
140
129
  Read-only tools run automatically. Write tools ask for your confirmation first. The *Write mode* column shows the minimum `OFW_WRITE_MODE` a tool needs to be available at all — see [Write protection](#write-protection-ofw_write_mode) below.
@@ -47,7 +47,7 @@ export async function loginWithPassword(username, password) {
47
47
  // OFW rejects bad credentials by re-serving its HTML login page (Spring
48
48
  // Security re-renders the form rather than returning 401/JSON). Surface a
49
49
  // clean, actionable message instead of dumping the HTML page — this is what
50
- // the hosted connector's login page shows the user on a failed sign-in.
50
+ // a hosted deployment's login page shows the user on a failed sign-in.
51
51
  if (contentType.includes('text/html')) {
52
52
  throw new Error('OFW login failed — your OurFamilyWizard email or password was not accepted. Check them and try again.');
53
53
  }
package/dist/bundle.js CHANGED
@@ -39074,7 +39074,7 @@ async function loginWithPassword(username, password) {
39074
39074
  // package.json
39075
39075
  var package_default = {
39076
39076
  name: "ofw-mcp",
39077
- version: "2.10.1",
39077
+ version: "2.10.2",
39078
39078
  license: "MIT",
39079
39079
  mcpName: "io.github.chrischall/ofw-mcp",
39080
39080
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -39103,10 +39103,7 @@ var package_default = {
39103
39103
  dev: "node --env-file=.env dist/index.js",
39104
39104
  test: "vitest run",
39105
39105
  "test:coverage": "vitest run --coverage",
39106
- "test:watch": "vitest",
39107
- "worker:dev": "wrangler dev",
39108
- "worker:deploy": "wrangler deploy",
39109
- "worker:test": "vitest run --config vitest.workers.config.ts"
39106
+ "test:watch": "vitest"
39110
39107
  },
39111
39108
  dependencies: {
39112
39109
  "@chrischall/mcp-utils": "^0.14.0",
@@ -39116,17 +39113,11 @@ var package_default = {
39116
39113
  zod: "^4.4.3"
39117
39114
  },
39118
39115
  devDependencies: {
39119
- "@chrischall/mcp-connector": "^1.1.1",
39120
- "@cloudflare/vitest-pool-workers": "^0.19.1",
39121
- "@cloudflare/workers-oauth-provider": "^0.8.1",
39122
- "@cloudflare/workers-types": "^5.20260708.1",
39123
39116
  "@types/node": "^26.0.0",
39124
39117
  "@vitest/coverage-v8": "^4.1.7",
39125
- agents: "^0.19.0",
39126
39118
  esbuild: "^0.28.0",
39127
39119
  typescript: "^7.0.2",
39128
- vitest: "^4.1.7",
39129
- wrangler: "^4.110.0"
39120
+ vitest: "^4.1.7"
39130
39121
  }
39131
39122
  };
39132
39123
 
@@ -39232,7 +39223,7 @@ var OFWClient = class {
39232
39223
  // Optional injected auth resolver. When set, the refresh callback uses it
39233
39224
  // instead of the module-level global `resolveAuth` (env-var → fetchproxy
39234
39225
  // priority). A hosted per-user deployment injects its own resolver so each
39235
- // request carries that user's credentials — see the Cloudflare Worker
39226
+ // request carries that user's credentials — see the per-user
39236
39227
  // deployment. Left undefined by the stdio path, which falls back to the
39237
39228
  // global resolver, keeping that behaviour byte-for-byte identical.
39238
39229
  authResolver;
@@ -43302,7 +43293,7 @@ var OFWCacheCore = class {
43302
43293
  }
43303
43294
  /**
43304
43295
  * Batch upsert every row in a single transaction — one round-trip's worth of
43305
- * work (crucial on the Durable Object backend, where each RPC is a subrequest).
43296
+ * work (crucial where each round trip is a billed request).
43306
43297
  * Empty array is a no-op (no transaction opened).
43307
43298
  */
43308
43299
  upsertMessages(rows) {
@@ -43445,8 +43436,8 @@ var OFWCacheCore = class {
43445
43436
  return r ? lineageFromDb(r) : null;
43446
43437
  }
43447
43438
  /**
43448
- * Batch read — one query for a whole page of drafts. On the Durable Object
43449
- * backend each cache call is a subrequest, so a per-draft lookup would spend
43439
+ * Batch read — one query for a whole page of drafts. Where the cache is
43440
+ * remote each cache call is a subrequest, so a per-draft lookup would spend
43450
43441
  * the caller's sync budget on bookkeeping.
43451
43442
  */
43452
43443
  getDraftLineageByIds(ids) {
@@ -43730,7 +43721,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
43730
43721
  var nodeAttachmentIO = new NodeAttachmentIO();
43731
43722
  await runMcp({
43732
43723
  name: "ofw",
43733
- version: "2.10.1",
43724
+ version: "2.10.2",
43734
43725
  // x-release-please-version
43735
43726
  deps: client,
43736
43727
  tools: [
@@ -5,8 +5,8 @@ import { OFWCacheCore, LocalCacheStore } from './store.js';
5
5
  // The `node:sqlite` backend for the OFW message cache — a local on-disk SQLite
6
6
  // file used by the stdio/desktop server. The query logic lives in OFWCacheCore
7
7
  // (src/cache/store.ts); this file only adapts `node:sqlite` to the SqlDriver
8
- // surface and manages the file handle + permissions. (The hosted Cloudflare
9
- // connector uses a Durable Object backend instead a later task.)
8
+ // surface and manages the file handle + permissions. Another deployment can
9
+ // adapt a different driver to the same surface.
10
10
  /** Adapts a `node:sqlite` DatabaseSync to the driver surface the core needs. */
11
11
  export class NodeSqlDriver {
12
12
  db;
@@ -3,9 +3,9 @@
3
3
  // All message reads (list/get/drafts/unread-sent) are served from this cache;
4
4
  // only ofw_sync_messages walks OFW for new content. The SQL lives here ONCE,
5
5
  // over a tiny synchronous {@link SqlDriver}, so the same schema/queries back
6
- // both engines: `node:sqlite` on the stdio/desktop server (src/cache/node.ts)
7
- // and a Durable Object's SQLite on the hosted Cloudflare connector (a later
8
- // task). This module imports nothing platform-specific.
6
+ // any engine: `node:sqlite` is the one that ships (src/cache/node.ts), and
7
+ // another deployment can adapt a different driver to the same surface. This
8
+ // module imports nothing platform-specific.
9
9
  function rowFromDb(r) {
10
10
  return {
11
11
  id: r.id,
@@ -131,7 +131,7 @@ export const SCHEMA_STATEMENTS = [
131
131
  * every open. SQLite has no `ADD COLUMN IF NOT EXISTS`, so each statement runs
132
132
  * inside a try/catch — re-running against an already-migrated DB throws
133
133
  * "duplicate column name", which is swallowed. Driver-agnostic: both
134
- * `node:sqlite` and the Durable Object's SQLite raise synchronously.
134
+ * `node:sqlite` raises synchronously, as any conforming driver must.
135
135
  */
136
136
  export const MIGRATIONS = [
137
137
  // Resumable deep-sync cursor. Absent/NULL → SyncState.resumePage null.
@@ -222,7 +222,7 @@ export class OFWCacheCore {
222
222
  }
223
223
  /**
224
224
  * Batch upsert every row in a single transaction — one round-trip's worth of
225
- * work (crucial on the Durable Object backend, where each RPC is a subrequest).
225
+ * work (crucial where each round trip is a billed request).
226
226
  * Empty array is a no-op (no transaction opened).
227
227
  */
228
228
  upsertMessages(rows) {
@@ -351,8 +351,8 @@ export class OFWCacheCore {
351
351
  return r ? lineageFromDb(r) : null;
352
352
  }
353
353
  /**
354
- * Batch read — one query for a whole page of drafts. On the Durable Object
355
- * backend each cache call is a subrequest, so a per-draft lookup would spend
354
+ * Batch read — one query for a whole page of drafts. Where the cache is
355
+ * remote each cache call is a subrequest, so a per-draft lookup would spend
356
356
  * the caller's sync budget on bookkeeping.
357
357
  */
358
358
  getDraftLineageByIds(ids) {
@@ -463,7 +463,7 @@ export class OFWCacheCore {
463
463
  }
464
464
  /**
465
465
  * Adapts a synchronous {@link OFWCacheCore} to the async {@link CacheStore}
466
- * interface. Used by the in-process node backend; the Durable Object backend
466
+ * interface. Used by the in-process node backend; a remote backend
467
467
  * implements CacheStore over a real RPC boundary instead.
468
468
  */
469
469
  export class LocalCacheStore {
package/dist/client.js CHANGED
@@ -6,16 +6,16 @@ import { resolveAuth } from './auth.js';
6
6
  import { BASE_URL, OFW_PROTOCOL_HEADERS, OFW_TOKEN_TTL_MS, OFW_TOKEN_EXPIRY_SKEW_MS } from './protocol.js';
7
7
  // Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb
8
8
  // bundle). loadDotenvSafely applies override:false + quiet:true and swallows a
9
- // missing dotenv module. The try/catch additionally guards the Cloudflare
10
- // Worker runtime, where `import.meta.url` is undefined and
11
- // `fileURLToPath(undefined)` would otherwise throw at module init (Worker
9
+ // missing dotenv module. The try/catch additionally guards a runtime where
10
+ // `import.meta.url` is undefined and `fileURLToPath(undefined)` would
11
+ // otherwise throw at module init (a failure at
12
12
  // startup validation) — there is no filesystem / .env to load there anyway.
13
13
  try {
14
14
  const dir = dirname(fileURLToPath(import.meta.url));
15
15
  await loadDotenvSafely({ path: join(dir, '..', '.env') });
16
16
  }
17
17
  catch {
18
- /* v8 ignore next -- only reached in a non-Node runtime (Workers): no .env to load */
18
+ /* v8 ignore next -- only reached in a non-Node runtime: no .env to load */
19
19
  }
20
20
  // Parse a Content-Disposition header for a filename. Prefers RFC 6266
21
21
  // `filename*=UTF-8''…` (percent-decoded) and falls back to `filename="…"`.
@@ -69,7 +69,7 @@ export class OFWClient {
69
69
  // Optional injected auth resolver. When set, the refresh callback uses it
70
70
  // instead of the module-level global `resolveAuth` (env-var → fetchproxy
71
71
  // priority). A hosted per-user deployment injects its own resolver so each
72
- // request carries that user's credentials — see the Cloudflare Worker
72
+ // request carries that user's credentials — see the per-user
73
73
  // deployment. Left undefined by the stdio path, which falls back to the
74
74
  // global resolver, keeping that behaviour byte-for-byte identical.
75
75
  authResolver;
package/dist/config.js CHANGED
@@ -144,16 +144,16 @@ export function getAutoRefreshStaleReads() {
144
144
  }
145
145
  // Default for ofw_download_attachment's `inline` arg when the caller doesn't
146
146
  // pass one. Set OFW_INLINE_ATTACHMENTS=true to have attachments returned as
147
- // MCP content blocks by default (skipping disk) — useful on sandboxed MCP
148
- // hosts where filesystem reads back to the model aren't available.
147
+ // MCP content blocks by default (skipping disk) — necessary wherever the
148
+ // caller cannot read the server's filesystem.
149
149
  export function getDefaultInlineAttachments() {
150
150
  return parseBoolEnv('OFW_INLINE_ATTACHMENTS');
151
151
  }
152
152
  /**
153
153
  * Per-invocation OFW-request budget for ofw_sync_messages.
154
154
  *
155
- * The hosted Cloudflare Worker connector enforces a subrequest cap per request
156
- * (every OFW API fetch and every Durable-Object cache RPC counts), so a deep
155
+ * A hosted deployment may enforce a request cap per call
156
+ * (every OFW API fetch and every cache round trip counts), so a deep
157
157
  * backfill must be bounded and resumable there. Set OFW_SYNC_MAX_REQUESTS to a
158
158
  * positive integer to cap the number of OFW requests one sync call may make
159
159
  * before pausing; the next call resumes the walk (deep or not) where it left off.
@@ -12,7 +12,7 @@
12
12
  // decompressed stream chunk by chunk, and abort the moment the running total
13
13
  // passes the limit. Peak memory is then bounded by the limit rather than by
14
14
  // whatever the file felt like claiming.
15
- /** 32 MiB. Sized to fit comfortably inside the Worker's memory budget. */
15
+ /** 32 MiB. Sized to fit comfortably inside a constrained memory budget. */
16
16
  export const MAX_DECOMPRESSED_BYTES = 32 * 1024 * 1024;
17
17
  /**
18
18
  * Thrown when decompression is aborted for exceeding its cap. Distinct from a
@@ -31,7 +31,7 @@ export class DecompressionLimitError extends Error {
31
31
  *
32
32
  * `deflate-raw` is the ZIP member format; `deflate` is the zlib-wrapped form a
33
33
  * PDF `/FlateDecode` stream uses. Both go through the WHATWG
34
- * `DecompressionStream` so this runs unchanged on Node and workerd.
34
+ * `DecompressionStream` so this runs unchanged wherever the standard exists.
35
35
  */
36
36
  export async function inflateBounded(data, format, limit, label) {
37
37
  const stream = new Blob([data]).stream()
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // The office formats are machine-generated XML with a known, flat shape per
4
4
  // part (rows of cells, runs of text), so a scanning reader beats pulling in a
5
- // DOM parser that has to run in both Node and workerd. The one rule this file
5
+ // DOM parser that has to run anywhere. The one rule this file
6
6
  // exists to enforce is that a tag match is anchored on the FULL tag name:
7
7
  // naively scanning for `<w:p` also matches `<w:pPr>`, which silently turns
8
8
  // paragraph properties into paragraphs.
@@ -4,9 +4,10 @@
4
4
  // reading one is the first step of every office-document extractor. This is
5
5
  // deliberately not a general ZIP library: it reads the central directory,
6
6
  // slices an entry's bytes, and inflates DEFLATE members via the WHATWG
7
- // `DecompressionStream` — which exists in BOTH Node ≥18 and workerd, so the
8
- // same code runs on the stdio server and the hosted connector. Using
9
- // `node:zlib` here would break the Worker build; adding a userland inflate
7
+ // `DecompressionStream` — a web standard available in Node ≥18 and in
8
+ // sandboxed runtimes alike, so the
9
+ // same code runs on the stdio server and a hosted deployment. Using
10
+ // `node:zlib` here would tie this to Node; adding a userland inflate
10
11
  // dependency would bloat it. Neither is necessary.
11
12
  import { inflateBounded, MAX_DECOMPRESSED_BYTES } from './inflate.js';
12
13
  const EOCD_SIG = 0x06054b50;
@@ -16,7 +17,7 @@ const ZIP64_SENTINEL = 0xffffffff;
16
17
  /**
17
18
  * Hard ceiling on a single decompressed member (32 MiB). An attachment is a
18
19
  * co-parent-supplied file, so a zip bomb is a real (if unlikely) input, and the
19
- * Worker's memory budget is what is being protected.
20
+ * memory budget of a constrained runtime is what is being protected.
20
21
  *
21
22
  * The cap is enforced on the bytes as they arrive ({@link inflateBounded}), NOT
22
23
  * on the size the archive declares for itself. The declared size is checked too
package/dist/index.js CHANGED
@@ -21,9 +21,9 @@ import { getCacheDbPath } from './config.js';
21
21
  import { NodeAttachmentIO } from './tools/attachments.js';
22
22
  // The stdio server backs the message cache with a local `node:sqlite` file,
23
23
  // opened lazily on first use (so the server still boots and answers the host's
24
- // install-time tools/list probe when no cache path is configured). The hosted
25
- // Cloudflare connector (a later task) injects a Durable-Object-backed
26
- // CacheStore + a filesystem-free AttachmentIO into the same registrar instead.
24
+ // install-time tools/list probe when no cache path is configured). Both the
25
+ // CacheStore and the AttachmentIO are injected, so a deployment with no usable
26
+ // disk supplies its own implementations to the same registrar instead.
27
27
  let nodeCache;
28
28
  const nodeCacheProvider = () => (nodeCache ??= OFWCache.open(getCacheDbPath()));
29
29
  const nodeAttachmentIO = new NodeAttachmentIO();
@@ -35,7 +35,7 @@ const nodeAttachmentIO = new NodeAttachmentIO();
35
35
  // always succeeds before any credential check runs.
36
36
  await runMcp({
37
37
  name: 'ofw',
38
- version: '2.10.1', // x-release-please-version
38
+ version: '2.10.2', // x-release-please-version
39
39
  deps: client,
40
40
  tools: [
41
41
  registerUserTools,
package/dist/sync.js CHANGED
@@ -227,8 +227,8 @@ async function walkPages(client, folder, folderId, opts, store) {
227
227
  }
228
228
  }
229
229
  // Flush the page's rows in one transaction/RPC. Skipped entirely when the
230
- // page held nothing new: on the Worker this call is a Durable-Object RPC,
231
- // and a DO RPC counts against the same subrequest budget as an OFW fetch.
230
+ // page held nothing new: where the cache is remote this call is a round
231
+ // trip, counting against the same subrequest budget as an OFW fetch.
232
232
  // A deep re-walk crosses page after page of already-cached messages, so an
233
233
  // unconditional "no-op" write spends the caller's budget to store nothing.
234
234
  if (toUpsert.length > 0)
@@ -294,7 +294,7 @@ export async function syncMessageFolder(client, folder, folderId, opts, store) {
294
294
  // This was a real starvation bug. `fwd.nextPage` is just the start page
295
295
  // (1) when nothing was fetched, so the `Math.min` below would silently
296
296
  // reset a deep backfill — e.g. resumePage 87 → 1 — discarding 86 pages
297
- // of progress. On the hosted Worker (OFW_SYNC_MAX_REQUESTS=40) a user
297
+ // of progress. Under a request budget (OFW_SYNC_MAX_REQUESTS) a user
298
298
  // with enough drafts to consume the whole budget, with drafts running
299
299
  // first, hit this on EVERY call: inbox/sent never got budget, their
300
300
  // cursor was reset every time, and the backfill could never advance.
@@ -502,7 +502,7 @@ export async function syncAll(client, opts, store) {
502
502
  // Drafts go FIRST. They are the only folder a destructive tool
503
503
  // (ofw_save_draft / ofw_delete_draft) reads as its base, and they are cheap
504
504
  // and bounded — one list page plus one detail per draft. Running them last,
505
- // behind inbox and sent, meant a bounded call (the Worker's
505
+ // behind inbox and sent, meant a bounded call (the
506
506
  // OFW_SYNC_MAX_REQUESTS=40) spent its whole budget backfilling history and
507
507
  // deferred drafts on every single call, so server-side draft edits stayed
508
508
  // invisible indefinitely while the response reported `drafts: 0`.
@@ -4,8 +4,8 @@
4
4
  // writes downloaded bytes to disk (and reads them back for the inline-reuse
5
5
  // path). Those are the ONLY node:fs touch points in the message tools — they
6
6
  // live behind this {@link AttachmentIO} interface so the stdio server can use
7
- // the disk-backed {@link NodeAttachmentIO} while the hosted Cloudflare
8
- // connector (a later task) injects an inline, filesystem-free implementation.
7
+ // the disk-backed {@link NodeAttachmentIO} while a deployment with no usable
8
+ // disk injects an inline, filesystem-free implementation.
9
9
  // Keeping the interface here means src/tools/messages.ts imports nothing from
10
10
  // node:fs.
11
11
  import { readFileSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
@@ -7,7 +7,7 @@ export class DraftFreshnessError extends Error {
7
7
  // FNV-1a (64-bit) over a canonical encoding. Not cryptographic — this is a
8
8
  // change detector, and it is never the sole guard: an unsupplied token falls
9
9
  // back to a full field-by-field comparison against the cached base.
10
- // BigInt keeps it byte-identical on node and on the Workers runtime.
10
+ // BigInt keeps it byte-identical across runtimes.
11
11
  const FNV_OFFSET = 0xcbf29ce484222325n;
12
12
  const FNV_PRIME = 0x100000001b3n;
13
13
  const MASK64 = 0xffffffffffffffffn;
@@ -164,10 +164,10 @@ function stateNote(state, cachedAsDraft, folderName) {
164
164
  * request for the map plus one per probed id.
165
165
  */
166
166
  export async function probeIds(client, store, ids, opts) {
167
- // THREE cache reads for the whole batch, not three per id. On the Durable
168
- // Object backend every cache call is a subrequest counting against the same
169
- // hosting cap as the OFW fetches, so a per-id lookup would spend the caller's
170
- // budget on bookkeeping before a single probe ran.
167
+ // THREE cache reads for the whole batch, not three per id. Where the cache
168
+ // is remote every call is a subrequest counting against the same hosting cap
169
+ // as the OFW fetches, so a per-id lookup would spend the caller's budget on
170
+ // bookkeeping before a single probe ran.
171
171
  const draftsById = new Map((await store.getDrafts(ids)).map((d) => [d.id, d]));
172
172
  const messagesById = new Map((await store.getMessages(ids)).map((m) => [m.id, m]));
173
173
  const prepared = ids.map((id) => {
@@ -294,9 +294,9 @@ export async function resolveDraftKey(store, draftKey) {
294
294
  return { currentId: chain[chain.length - 1].id, ids: chain.map((r) => r.id) };
295
295
  }
296
296
  /**
297
- * Mint a new stable draft identity. Uses the Web Crypto global, which both
298
- * Node ≥19 and the Workers runtime provide — a `node:crypto` import would not
299
- * bundle for the hosted connector.
297
+ * Mint a new stable draft identity. Uses the Web Crypto global, which Node ≥19
298
+ * provides natively — a `node:crypto` import would not bundle for a hosted
299
+ * deployment.
300
300
  */
301
301
  export function newDraftKey() {
302
302
  return `dk_${crypto.randomUUID()}`;
@@ -81,7 +81,7 @@ const FolderCountsSchema = z.looseObject({
81
81
  /**
82
82
  * Cap on per-id probes in one ofw_check_freshness call.
83
83
  *
84
- * Each id costs one OFW request, and on the hosted Worker every request counts
84
+ * Each id costs one OFW request, and under a request budget every one counts
85
85
  * against the subrequest cap (see OFW_SYNC_MAX_REQUESTS). The check has to stay
86
86
  * cheap enough that a caller reaches for it freely — that is the entire point
87
87
  * of it existing — so it truncates loudly rather than turning into a sync.
@@ -885,7 +885,7 @@ export function registerMessageTools(server, client, cacheProvider, attachmentIO
885
885
  const { freshness, serverConfirmed, cacheStatus } = await draftsFreshness(cache);
886
886
  const rows = await cache.listDrafts({ page, size });
887
887
  const total = await cache.countDrafts();
888
- // One batch lookup for the whole page — on the Durable Object backend a
888
+ // One batch lookup for the whole page — where the cache is remote a
889
889
  // per-draft lineage read would be a subrequest each.
890
890
  const keyById = new Map((await cache.getDraftLineageByIds(rows.map((d) => d.id))).map((l) => [l.id, l.draftKey]));
891
891
  // Every draft carries the concurrency token to echo back on a write, its
@@ -1250,7 +1250,7 @@ export function registerMessageTools(server, client, cacheProvider, attachmentIO
1250
1250
  },
1251
1251
  }, async (args) => {
1252
1252
  // Resolve the upload source through the injected attachment-I/O boundary
1253
- // (disk read on node; an in-memory source on the hosted connector).
1253
+ // (disk read on node; an in-memory source on a hosted deployment).
1254
1254
  const { blob, fileName, mimeType: mime, sizeBytes } = await attachmentIO.resolveUpload(args.path);
1255
1255
  // Build the multipart payload matching the OFW web UI's request shape.
1256
1256
  const form = new FormData();
@@ -1298,7 +1298,7 @@ export function registerMessageTools(server, client, cacheProvider, attachmentIO
1298
1298
  const fileId = args.fileId;
1299
1299
  const cache = cacheProvider();
1300
1300
  const requestedInline = args.inline ?? getDefaultInlineAttachments();
1301
- // When the deployment has no filesystem (hosted connector), inline is the
1301
+ // When the deployment has no filesystem, inline is the
1302
1302
  // ONLY path to the bytes — force it rather than erroring on a disk write.
1303
1303
  // `forcedInline` records that we overrode an explicit `inline:false` so the
1304
1304
  // response is honest about it instead of silently ignoring the argument.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.10.1",
3
+ "version": "2.10.2",
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)",
@@ -29,10 +29,7 @@
29
29
  "dev": "node --env-file=.env dist/index.js",
30
30
  "test": "vitest run",
31
31
  "test:coverage": "vitest run --coverage",
32
- "test:watch": "vitest",
33
- "worker:dev": "wrangler dev",
34
- "worker:deploy": "wrangler deploy",
35
- "worker:test": "vitest run --config vitest.workers.config.ts"
32
+ "test:watch": "vitest"
36
33
  },
37
34
  "dependencies": {
38
35
  "@chrischall/mcp-utils": "^0.14.0",
@@ -42,16 +39,10 @@
42
39
  "zod": "^4.4.3"
43
40
  },
44
41
  "devDependencies": {
45
- "@chrischall/mcp-connector": "^1.1.1",
46
- "@cloudflare/vitest-pool-workers": "^0.19.1",
47
- "@cloudflare/workers-oauth-provider": "^0.8.1",
48
- "@cloudflare/workers-types": "^5.20260708.1",
49
42
  "@types/node": "^26.0.0",
50
43
  "@vitest/coverage-v8": "^4.1.7",
51
- "agents": "^0.19.0",
52
44
  "esbuild": "^0.28.0",
53
45
  "typescript": "^7.0.2",
54
- "vitest": "^4.1.7",
55
- "wrangler": "^4.110.0"
46
+ "vitest": "^4.1.7"
56
47
  }
57
48
  }
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.1",
9
+ "version": "2.10.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.10.1",
14
+ "version": "2.10.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -10,7 +10,6 @@ MCP server for OurFamilyWizard — provides read/write access to messages, calen
10
10
  - **npm:** [npmjs.com/package/ofw-mcp](https://www.npmjs.com/package/ofw-mcp)
11
11
  - **Source:** [github.com/chrischall/ofw-mcp](https://github.com/chrischall/ofw-mcp)
12
12
 
13
- > These tools are also available via the hosted [claude.ai](https://claude.ai) remote connector (a Cloudflare Worker) — the tool set and behaviour are identical to the local stdio install. See the repo's `docs/DEPLOY-CONNECTOR.md`.
14
13
 
15
14
  ## Setup
16
15
 
package/dist/ofw-auth.js DELETED
@@ -1,26 +0,0 @@
1
- import { loginWithPassword } from './auth-password.js';
2
- /**
3
- * `ConnectorAuth` for the OurFamilyWizard remote connector: the login page
4
- * collects the user's own OFW email/username + password, verifies them via the
5
- * same Spring Security form login the stdio server uses (`loginWithPassword` in
6
- * `auth-password.js`), and stores `{ username, password }` as the OAuth props
7
- * that `worker.ts`'s `buildClient` turns into a per-user `OFWClient` capable of
8
- * re-authenticating when its 6h token expires.
9
- */
10
- export const ofwAuth = {
11
- service: 'OurFamilyWizard',
12
- accent: '#00A9A5',
13
- privacyNote: 'Your OFW email and password are stored encrypted and used only to sign in to OurFamilyWizard on your behalf ' +
14
- '(OFW sign-in tokens expire every few hours, so your password is needed to renew them).',
15
- fields: [
16
- { name: 'username', label: 'OFW email or username' },
17
- { name: 'password', label: 'OFW password', type: 'password' },
18
- ],
19
- async login(fields) {
20
- // Verify the credentials up front — a bad password throws here, which the
21
- // connector surfaces back on the login page. We deliberately discard the
22
- // returned token: the per-user client logs in again from the stored creds.
23
- await loginWithPassword(fields.username, fields.password);
24
- return { username: fields.username, password: fields.password };
25
- },
26
- };