ofw-mcp 2.9.1 → 2.9.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.9.1"
9
+ "version": "2.9.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.9.1",
17
+ "version": "2.9.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.9.1",
4
+ "version": "2.9.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/dist/bundle.js CHANGED
@@ -34929,7 +34929,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
34929
34929
  "capture_redirect",
34930
34930
  "read_indexed_db",
34931
34931
  "read_dom",
34932
- "download"
34932
+ "download",
34933
+ "graphql"
34933
34934
  ]);
34934
34935
 
34935
34936
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35232,6 +35233,7 @@ function assertIndexedDbScopesArray(value, label) {
35232
35233
  }
35233
35234
  var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
35234
35235
  var DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
35236
+ var GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
35235
35237
  function assertDomSelectorsArray(value, label) {
35236
35238
  if (!Array.isArray(value)) {
35237
35239
  throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
@@ -35268,6 +35270,37 @@ function assertDomSelectorsArray(value, label) {
35268
35270
  }
35269
35271
  }
35270
35272
  }
35273
+ function assertGraphqlOpsArray(value, label) {
35274
+ if (!Array.isArray(value)) {
35275
+ throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
35276
+ }
35277
+ const seen = /* @__PURE__ */ new Set();
35278
+ for (let i = 0; i < value.length; i++) {
35279
+ const entry = value[i];
35280
+ assertObject(entry, `${label}[${i}]`);
35281
+ if (entry.name === void 0) {
35282
+ throw new ProtocolError(`${label}[${i}].name: missing`);
35283
+ }
35284
+ if (entry.operationName === void 0) {
35285
+ throw new ProtocolError(`${label}[${i}].operationName: missing`);
35286
+ }
35287
+ if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
35288
+ throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
35289
+ }
35290
+ if (typeof entry.operationName !== "string" || !GRAPHQL_OP_NAME_RE.test(entry.operationName)) {
35291
+ throw new ProtocolError(`${label}[${i}].operationName: invalid ${JSON.stringify(entry.operationName)}`);
35292
+ }
35293
+ if (seen.has(entry.name)) {
35294
+ throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
35295
+ }
35296
+ seen.add(entry.name);
35297
+ for (const k of Object.keys(entry)) {
35298
+ if (k !== "name" && k !== "operationName") {
35299
+ throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
35300
+ }
35301
+ }
35302
+ }
35303
+ }
35271
35304
  function validateFrame(raw) {
35272
35305
  assertObject(raw, "frame");
35273
35306
  const t = raw.type;
@@ -35346,6 +35379,9 @@ function validateHello(raw) {
35346
35379
  if (raw.domSelectors !== void 0) {
35347
35380
  assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
35348
35381
  }
35382
+ if (raw.graphqlOps !== void 0) {
35383
+ assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
35384
+ }
35349
35385
  assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
35350
35386
  assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
35351
35387
  assertBase64(raw.sessionNonce, "hello.sessionNonce");
@@ -35595,6 +35631,28 @@ function validateInnerRequest(raw) {
35595
35631
  }
35596
35632
  return raw;
35597
35633
  }
35634
+ if (raw.op === "graphql_query") {
35635
+ assertObject(raw.init, "inner.init");
35636
+ if (raw.init.name === void 0)
35637
+ throw new ProtocolError("inner.init.name: missing");
35638
+ if (raw.init.variables === void 0) {
35639
+ throw new ProtocolError("inner.init.variables: missing");
35640
+ }
35641
+ assertString(raw.init.name, "inner.init.name");
35642
+ if (raw.init.name.length === 0) {
35643
+ throw new ProtocolError("inner.init.name: must be non-empty");
35644
+ }
35645
+ assertObject(raw.init.variables, "inner.init.variables");
35646
+ if (raw.init.tabUrl !== void 0) {
35647
+ assertString(raw.init.tabUrl, "inner.init.tabUrl");
35648
+ }
35649
+ for (const k of Object.keys(raw.init)) {
35650
+ if (k !== "name" && k !== "variables" && k !== "tabUrl") {
35651
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on graphql_query`);
35652
+ }
35653
+ }
35654
+ return raw;
35655
+ }
35598
35656
  if (raw.op === "download") {
35599
35657
  assertObject(raw.init, "inner.init");
35600
35658
  if (raw.init.url === void 0) {
@@ -35620,7 +35678,7 @@ function validateInnerRequest(raw) {
35620
35678
  }
35621
35679
  return raw;
35622
35680
  }
35623
- throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download"; got ${JSON.stringify(raw.op)}`);
35681
+ throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query"; got ${JSON.stringify(raw.op)}`);
35624
35682
  }
35625
35683
  function assertNonEmptyKeyArray(value, label) {
35626
35684
  if (!Array.isArray(value)) {
@@ -35651,6 +35709,10 @@ function assertStringMap(value, label) {
35651
35709
  }
35652
35710
  }
35653
35711
  }
35712
+ var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
35713
+ ...KNOWN_CAPABILITIES,
35714
+ "graphql_query"
35715
+ ]);
35654
35716
  function validateInnerResponse(raw) {
35655
35717
  assertPositiveInt(raw.id, "inner.id");
35656
35718
  if (raw.ok === true) {
@@ -35712,6 +35774,13 @@ function validateInnerResponse(raw) {
35712
35774
  assertStringMap(raw.values, "inner.values");
35713
35775
  return raw;
35714
35776
  }
35777
+ if (op === "graphql_query") {
35778
+ if (raw.data === void 0) {
35779
+ throw new ProtocolError("inner.data: missing on graphql_query response");
35780
+ }
35781
+ assertObject(raw.data, "inner.data");
35782
+ return raw;
35783
+ }
35715
35784
  if (op === "download") {
35716
35785
  assertObject(raw.value, "inner.value");
35717
35786
  assertString(raw.value.path, "inner.value.path");
@@ -35736,7 +35805,7 @@ function validateInnerResponse(raw) {
35736
35805
  if (raw.ok === false) {
35737
35806
  assertString(raw.error, "inner.error");
35738
35807
  if (raw.op !== void 0) {
35739
- if (typeof raw.op !== "string" || !KNOWN_CAPABILITIES.has(raw.op)) {
35808
+ if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
35740
35809
  throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
35741
35810
  }
35742
35811
  }
@@ -35928,11 +35997,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
35928
35997
  };
35929
35998
  }
35930
35999
  async function openEncryptedFrame(sessionKey, frame) {
35931
- const iv = fromB64(frame.iv);
35932
- const ct = fromB64(frame.ciphertext);
35933
- const pt = await aesGcmOpen(sessionKey, iv, ct);
35934
- const parsed = JSON.parse(dec.decode(pt));
35935
- return validateInnerFrame(parsed);
36000
+ const result = await openEncryptedFrameDetailed(sessionKey, frame);
36001
+ if (result.stage === "ok")
36002
+ return result.inner;
36003
+ throw result.error instanceof Error ? result.error : new Error(String(result.error));
36004
+ }
36005
+ async function openEncryptedFrameDetailed(sessionKey, frame) {
36006
+ let pt;
36007
+ try {
36008
+ const iv = fromB64(frame.iv);
36009
+ const ct = fromB64(frame.ciphertext);
36010
+ pt = await aesGcmOpen(sessionKey, iv, ct);
36011
+ } catch (error51) {
36012
+ return { stage: "decrypt-failed", error: error51 };
36013
+ }
36014
+ let parsed;
36015
+ try {
36016
+ parsed = JSON.parse(dec.decode(pt));
36017
+ } catch (error51) {
36018
+ return { stage: "validation-failed", error: error51, recoveredId: void 0 };
36019
+ }
36020
+ try {
36021
+ const inner = validateInnerFrame(parsed);
36022
+ return { stage: "ok", inner };
36023
+ } catch (error51) {
36024
+ const recoveredId = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.id === "number" && Number.isInteger(parsed.id) && parsed.id > 0 ? parsed.id : void 0;
36025
+ return { stage: "validation-failed", error: error51, recoveredId };
36026
+ }
35936
36027
  }
35937
36028
 
35938
36029
  // node_modules/@fetchproxy/server/dist/election.js
@@ -36058,6 +36149,12 @@ async function buildServerHello(opts) {
36058
36149
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36059
36150
  }));
36060
36151
  }
36152
+ if (opts.graphqlOps && opts.graphqlOps.length > 0) {
36153
+ hello.graphqlOps = opts.graphqlOps.map((d) => ({
36154
+ name: d.name,
36155
+ operationName: d.operationName
36156
+ }));
36157
+ }
36061
36158
  return hello;
36062
36159
  }
36063
36160
 
@@ -36148,7 +36245,8 @@ async function startHost(opts) {
36148
36245
  indexedDbScopes: opts.ownIndexedDbScopes,
36149
36246
  localStoragePointers: opts.ownLocalStoragePointers,
36150
36247
  sessionStoragePointers: opts.ownSessionStoragePointers,
36151
- domSelectors: opts.ownDomSelectors
36248
+ domSelectors: opts.ownDomSelectors,
36249
+ graphqlOps: opts.ownGraphqlOps
36152
36250
  });
36153
36251
  const ownSessionNonce = fromB64(ownHello.sessionNonce);
36154
36252
  let extensionWs = null;
@@ -36385,7 +36483,8 @@ async function startPeer(opts) {
36385
36483
  indexedDbScopes: opts.indexedDbScopes,
36386
36484
  domSelectors: opts.domSelectors,
36387
36485
  localStoragePointers: opts.localStoragePointers,
36388
- sessionStoragePointers: opts.sessionStoragePointers
36486
+ sessionStoragePointers: opts.sessionStoragePointers,
36487
+ graphqlOps: opts.graphqlOps
36389
36488
  });
36390
36489
  const sessionNonce = fromB64(hello.sessionNonce);
36391
36490
  ws.send(JSON.stringify(hello));
@@ -36429,10 +36528,20 @@ async function startPeer(opts) {
36429
36528
  return;
36430
36529
  if (!session.acceptInboundSeq(frame.seq))
36431
36530
  return;
36432
- try {
36433
- const inner = await openEncryptedFrame(session.sessionKey, frame);
36434
- innerListeners.forEach((cb) => cb(inner));
36435
- } catch {
36531
+ const result = await openEncryptedFrameDetailed(session.sessionKey, frame);
36532
+ if (result.stage === "ok") {
36533
+ innerListeners.forEach((cb) => cb(result.inner));
36534
+ } else if (result.stage === "decrypt-failed") {
36535
+ } else {
36536
+ console.error("[fetchproxy] peer: received a frame that decrypted OK but failed validation:", result.error);
36537
+ if (result.recoveredId !== void 0) {
36538
+ innerListeners.forEach((cb) => cb({
36539
+ type: "response",
36540
+ id: result.recoveredId,
36541
+ ok: false,
36542
+ error: `malformed response failed protocol validation: ${String(result.error)}`
36543
+ }));
36544
+ }
36436
36545
  }
36437
36546
  }
36438
36547
  } catch (e) {
@@ -36709,6 +36818,10 @@ var FetchproxyServer = class {
36709
36818
  pendingIdb = /* @__PURE__ */ new Map();
36710
36819
  // download awaiters resolve the saved-file metadata (path + size + mime).
36711
36820
  pendingDownload = /* @__PURE__ */ new Map();
36821
+ // 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
36822
+ // shape is operation-specific, so the awaiter resolves `unknown` and the
36823
+ // caller narrows.
36824
+ pendingGraphql = /* @__PURE__ */ new Map();
36712
36825
  mcpId = null;
36713
36826
  identity = null;
36714
36827
  // 0.5.3+: in-flight role-election / handle-start promise. Set the
@@ -36796,6 +36909,10 @@ var FetchproxyServer = class {
36796
36909
  selector: d.selector,
36797
36910
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36798
36911
  })),
36912
+ graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
36913
+ name: d.name,
36914
+ operationName: d.operationName
36915
+ })),
36799
36916
  // 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
36800
36917
  // adapter was about to set these to the same numbers anyway; the
36801
36918
  // back-door is `0` (explicit opt-out) if a caller genuinely wants
@@ -36917,6 +37034,7 @@ var FetchproxyServer = class {
36917
37034
  ownLocalStoragePointers: this.opts.localStoragePointers,
36918
37035
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
36919
37036
  ownDomSelectors: this.opts.domSelectors,
37037
+ ownGraphqlOps: this.opts.graphqlOps,
36920
37038
  onPairCode: this.opts.onPairCode
36921
37039
  });
36922
37040
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
@@ -36945,7 +37063,8 @@ var FetchproxyServer = class {
36945
37063
  indexedDbScopes: this.opts.indexedDbScopes,
36946
37064
  localStoragePointers: this.opts.localStoragePointers,
36947
37065
  sessionStoragePointers: this.opts.sessionStoragePointers,
36948
- domSelectors: this.opts.domSelectors
37066
+ domSelectors: this.opts.domSelectors,
37067
+ graphqlOps: this.opts.graphqlOps
36949
37068
  });
36950
37069
  this.peerHandle.onInner((inner) => this.onInner(inner));
36951
37070
  this.peerHandle.onRenegotiate(() => {
@@ -37151,6 +37270,7 @@ var FetchproxyServer = class {
37151
37270
  this.pendingRedirect.delete(id);
37152
37271
  this.pendingDownload.delete(id);
37153
37272
  this.pendingIdb.delete(id);
37273
+ this.pendingGraphql.delete(id);
37154
37274
  }
37155
37275
  throw err;
37156
37276
  }
@@ -37960,6 +38080,52 @@ var FetchproxyServer = class {
37960
38080
  await this.sendInnerFrame(inner);
37961
38081
  return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
37962
38082
  }
38083
+ /**
38084
+ * 1.x+: run a declared GraphQL operation through the page's own Apollo
38085
+ * client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
38086
+ * Requires `'graphql'` in capabilities AND `name` to match a declared
38087
+ * `graphqlOps` entry. The extension resolves `name` → `operationName` →
38088
+ * the live DocumentNode the page already observed, then invokes
38089
+ * `client.query({ query, variables })` — the site's own request path, so
38090
+ * per-request bot telemetry (Akamai etc.) runs automatically.
38091
+ *
38092
+ * Returns the GraphQL `data` object on success (shape is
38093
+ * operation-specific; the caller narrows). Throws a plain `Error` on
38094
+ * developer mistakes (undeclared capability, undeclared name) and a
38095
+ * descriptive `Error` on the `ok:false` bridge path — which includes the
38096
+ * typed "operation not yet observed on this tab" case (open the site's
38097
+ * page and retry).
38098
+ */
38099
+ async graphqlQuery(opts) {
38100
+ if (!this.opts.capabilities.includes("graphql")) {
38101
+ throw new Error('FetchproxyServer.graphqlQuery(): MCP did not declare "graphql" in capabilities');
38102
+ }
38103
+ if (typeof opts.name !== "string" || opts.name.length === 0) {
38104
+ throw new Error("FetchproxyServer.graphqlQuery: opts.name must be a non-empty string");
38105
+ }
38106
+ const declaredNames = this.opts.graphqlOps.map((d) => d.name);
38107
+ if (!declaredNames.includes(opts.name)) {
38108
+ throw new Error(`FetchproxyServer.graphqlQuery: operation ${JSON.stringify(opts.name)} not in declared graphqlOps [${declaredNames.map((n) => JSON.stringify(n)).join(", ")}]`);
38109
+ }
38110
+ await this.ensureConnected();
38111
+ this.throwIfPendingPair();
38112
+ const id = this.nextRequestId++;
38113
+ const inner = {
38114
+ type: "request",
38115
+ id,
38116
+ op: "graphql_query",
38117
+ init: {
38118
+ name: opts.name,
38119
+ variables: opts.variables,
38120
+ ...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
38121
+ }
38122
+ };
38123
+ const pending = new Promise((resolve2, reject) => {
38124
+ this.pendingGraphql.set(id, { resolve: resolve2, reject });
38125
+ });
38126
+ await this.sendInnerFrame(inner);
38127
+ return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
38128
+ }
37963
38129
  assertScopeSubset(requested, declared, label) {
37964
38130
  const undeclared = undeclaredKeys(requested, declared);
37965
38131
  if (undeclared.length > 0) {
@@ -38097,6 +38263,20 @@ var FetchproxyServer = class {
38097
38263
  }
38098
38264
  return;
38099
38265
  }
38266
+ const graphqlCb = this.pendingGraphql.get(inner.id);
38267
+ if (graphqlCb) {
38268
+ this.pendingGraphql.delete(inner.id);
38269
+ if (inner.ok) {
38270
+ if (inner.op === "graphql_query") {
38271
+ graphqlCb.resolve(inner.data);
38272
+ } else {
38273
+ graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
38274
+ }
38275
+ } else {
38276
+ graphqlCb.reject(new FetchproxyProtocolError(inner.error));
38277
+ }
38278
+ return;
38279
+ }
38100
38280
  const cookiesCb = this.pendingReadCookies.get(inner.id);
38101
38281
  if (cookiesCb) {
38102
38282
  this.pendingReadCookies.delete(inner.id);
@@ -38148,6 +38328,9 @@ var FetchproxyServer = class {
38148
38328
  for (const { reject } of this.pendingDownload.values())
38149
38329
  reject(err);
38150
38330
  this.pendingDownload.clear();
38331
+ for (const { reject } of this.pendingGraphql.values())
38332
+ reject(err);
38333
+ this.pendingGraphql.clear();
38151
38334
  }
38152
38335
  /**
38153
38336
  * 0.5.2+: read the current pair-pending pair code from whichever handle
@@ -38407,7 +38590,7 @@ async function loginWithPassword(username, password) {
38407
38590
  // package.json
38408
38591
  var package_default = {
38409
38592
  name: "ofw-mcp",
38410
- version: "2.9.1",
38593
+ version: "2.9.2",
38411
38594
  license: "MIT",
38412
38595
  mcpName: "io.github.chrischall/ofw-mcp",
38413
38596
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -38442,8 +38625,8 @@ var package_default = {
38442
38625
  "worker:test": "vitest run --config vitest.workers.config.ts"
38443
38626
  },
38444
38627
  dependencies: {
38445
- "@chrischall/mcp-utils": "^0.13.0",
38446
- "@fetchproxy/bootstrap": "^1.3.0",
38628
+ "@chrischall/mcp-utils": "^0.14.0",
38629
+ "@fetchproxy/bootstrap": "^1.7.0",
38447
38630
  "@modelcontextprotocol/sdk": "^1.29.0",
38448
38631
  dotenv: "^17.4.2",
38449
38632
  zod: "^4.4.3"
@@ -42911,7 +43094,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
42911
43094
  var nodeAttachmentIO = new NodeAttachmentIO();
42912
43095
  await runMcp({
42913
43096
  name: "ofw",
42914
- version: "2.9.1",
43097
+ version: "2.9.2",
42915
43098
  // x-release-please-version
42916
43099
  deps: client,
42917
43100
  tools: [
package/dist/index.js CHANGED
@@ -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.9.1', // x-release-please-version
38
+ version: '2.9.2', // x-release-please-version
39
39
  deps: client,
40
40
  tools: [
41
41
  registerUserTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.9.1",
3
+ "version": "2.9.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)",
@@ -35,8 +35,8 @@
35
35
  "worker:test": "vitest run --config vitest.workers.config.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@chrischall/mcp-utils": "^0.13.0",
39
- "@fetchproxy/bootstrap": "^1.3.0",
38
+ "@chrischall/mcp-utils": "^0.14.0",
39
+ "@fetchproxy/bootstrap": "^1.7.0",
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
41
  "dotenv": "^17.4.2",
42
42
  "zod": "^4.4.3"
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.9.1",
9
+ "version": "2.9.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.9.1",
14
+ "version": "2.9.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },