ofw-mcp 2.10.0 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +0 -11
- package/dist/auth-password.js +1 -1
- package/dist/bundle.js +594 -119
- package/dist/cache/node.js +2 -2
- package/dist/cache/store.js +8 -8
- package/dist/client.js +5 -5
- package/dist/config.js +4 -4
- package/dist/extract/inflate.js +2 -2
- package/dist/extract/xml.js +1 -1
- package/dist/extract/zip.js +5 -4
- package/dist/index.js +4 -4
- package/dist/sync.js +4 -4
- package/dist/tools/attachments.js +2 -2
- package/dist/tools/draft-freshness.js +1 -1
- package/dist/tools/lifecycle.js +7 -7
- package/dist/tools/messages.js +4 -4
- package/package.json +4 -13
- package/server.json +2 -2
- package/skills/ofw/SKILL.md +0 -1
- package/dist/ofw-auth.js +0 -26
package/dist/cache/node.js
CHANGED
|
@@ -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.
|
|
9
|
-
//
|
|
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;
|
package/dist/cache/store.js
CHANGED
|
@@ -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
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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`
|
|
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
|
|
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.
|
|
355
|
-
*
|
|
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;
|
|
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
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
|
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
|
|
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) —
|
|
148
|
-
//
|
|
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
|
-
*
|
|
156
|
-
* (every OFW API fetch and every
|
|
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.
|
package/dist/extract/inflate.js
CHANGED
|
@@ -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
|
|
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
|
|
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()
|
package/dist/extract/xml.js
CHANGED
|
@@ -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
|
|
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.
|
package/dist/extract/zip.js
CHANGED
|
@@ -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` —
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
-
*
|
|
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).
|
|
25
|
-
//
|
|
26
|
-
//
|
|
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.
|
|
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:
|
|
231
|
-
//
|
|
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.
|
|
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
|
|
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
|
|
8
|
-
//
|
|
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
|
|
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;
|
package/dist/tools/lifecycle.js
CHANGED
|
@@ -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.
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
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
|
|
298
|
-
*
|
|
299
|
-
*
|
|
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()}`;
|
package/dist/tools/messages.js
CHANGED
|
@@ -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
|
|
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 —
|
|
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
|
|
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
|
|
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.
|
|
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,29 +29,20 @@
|
|
|
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",
|
|
39
|
-
"@fetchproxy/bootstrap": "^
|
|
36
|
+
"@fetchproxy/bootstrap": "^2.0.0",
|
|
40
37
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
38
|
"dotenv": "^17.4.2",
|
|
42
39
|
"zod": "^4.4.3"
|
|
43
40
|
},
|
|
44
41
|
"devDependencies": {
|
|
45
|
-
"@chrischall/mcp-connector": "^1.1.1",
|
|
46
|
-
"@cloudflare/vitest-pool-workers": "^0.18.4",
|
|
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.
|
|
9
|
+
"version": "2.10.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.10.
|
|
14
|
+
"version": "2.10.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/skills/ofw/SKILL.md
CHANGED
|
@@ -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
|
-
};
|