ofw-mcp 2.18.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "OurFamilyWizard tools for Claude Code",
9
- "version": "2.18.0"
9
+ "version": "2.19.0"
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.18.0",
17
+ "version": "2.19.0",
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.18.0",
4
+ "version": "2.19.0",
5
5
  "description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall"
@@ -9,6 +9,7 @@
9
9
  // This file exists as a standalone helper (not a method on `OFWClient`) so
10
10
  // `resolveAuth()` in `./auth.ts` can call it without a Client instance, and
11
11
  // so tests can mock it at the module boundary.
12
+ import { currentCallSignal } from '@chrischall/mcp-utils';
12
13
  import { BASE_URL, OFW_PROTOCOL_HEADERS, OFW_TOKEN_TTL_MS, assertOfwUrl } from './protocol.js';
13
14
  export async function loginWithPassword(username, password) {
14
15
  // Step 1: get a SESSION cookie (Spring Security refuses the POST without it).
@@ -17,6 +18,10 @@ export async function loginWithPassword(username, password) {
17
18
  const initResponse = await fetch(initUrl, {
18
19
  headers: { ...OFW_PROTOCOL_HEADERS },
19
20
  redirect: 'manual',
21
+ // Honour a caller who has given up (mcp-utils `cancel`). A sign-in
22
+ // nobody is waiting for should not keep hitting OFW, which counts
23
+ // failed attempts against the account.
24
+ signal: currentCallSignal(),
20
25
  });
21
26
  // headers.get('set-cookie') folds multiple Set-Cookie headers into one
22
27
  // comma-joined string; getSetCookie() preserves them individually. Echo
@@ -30,6 +35,7 @@ export async function loginWithPassword(username, password) {
30
35
  assertOfwUrl(loginUrl);
31
36
  const response = await fetch(loginUrl, {
32
37
  method: 'POST',
38
+ signal: currentCallSignal(),
33
39
  headers: {
34
40
  ...OFW_PROTOCOL_HEADERS,
35
41
  Accept: 'application/json',
package/dist/bundle.js CHANGED
@@ -3710,6 +3710,24 @@ var require_websocket_server = __commonJS({
3710
3710
  }
3711
3711
  });
3712
3712
 
3713
+ // node_modules/@chrischall/mcp-utils/dist/cancel/index.js
3714
+ import { AsyncLocalStorage } from "node:async_hooks";
3715
+ var storage = new AsyncLocalStorage();
3716
+ function withCallSignal(signal, fn) {
3717
+ return signal ? storage.run({ signal }, fn) : fn();
3718
+ }
3719
+ function currentCallSignal() {
3720
+ return storage.getStore()?.signal;
3721
+ }
3722
+ function withAmbientCancellation(own2) {
3723
+ const ambient = currentCallSignal();
3724
+ if (!ambient)
3725
+ return own2;
3726
+ if (!own2)
3727
+ return ambient;
3728
+ return AbortSignal.any([own2, ambient]);
3729
+ }
3730
+
3713
3731
  // node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
3714
3732
  var __create2 = Object.create;
3715
3733
  var __defProp2 = Object.defineProperty;
@@ -38155,17 +38173,41 @@ Hint: ${err.hint}`);
38155
38173
  }
38156
38174
  throw err;
38157
38175
  }
38176
+ function callSignalFrom(args) {
38177
+ const ctx = args.at(-1);
38178
+ if (typeof ctx !== "object" || ctx === null)
38179
+ return void 0;
38180
+ const req = ctx.mcpReq;
38181
+ if (typeof req !== "object" || req === null)
38182
+ return void 0;
38183
+ const signal = req.signal;
38184
+ return signal instanceof AbortSignal ? signal : void 0;
38185
+ }
38158
38186
  function surfaceToolHints(server) {
38159
38187
  const register2 = server.registerTool.bind(server);
38160
- server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => {
38161
- let result;
38162
- try {
38163
- result = cb(...args);
38164
- } catch (err) {
38165
- return hintResultOrRethrow(err);
38166
- }
38167
- return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
38168
- });
38188
+ server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
38189
+ // THE CALLER'S CANCELLATION, made ambient for the whole handler
38190
+ // (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
38191
+ // measured against @modelcontextprotocol/server 2.0.0, a cancelled
38192
+ // call aborts `ctx.mcpReq.signal` with the caller's reason and the
38193
+ // handler runs to completion regardless — so the HTTP request stays
38194
+ // in flight, the child keeps burning metered CPU, and the upstream
38195
+ // keeps being hit for somebody who has gone. claude.ai sent 101
38196
+ // cancellations in the week to 2026-09-20.
38197
+ //
38198
+ // Here because this is the one wrapper every tool already passes
38199
+ // through: threading the signal by hand would mean editing several
38200
+ // hundred handlers and missing exactly the ones nobody edits.
38201
+ withCallSignal(callSignalFrom(args), () => {
38202
+ let result;
38203
+ try {
38204
+ result = cb(...args);
38205
+ } catch (err) {
38206
+ return hintResultOrRethrow(err);
38207
+ }
38208
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
38209
+ })
38210
+ ));
38169
38211
  }
38170
38212
  async function createMcpServer(opts) {
38171
38213
  const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
@@ -43922,13 +43964,18 @@ async function loginWithPassword(username, password) {
43922
43964
  assertOfwUrl(initUrl);
43923
43965
  const initResponse = await fetch(initUrl, {
43924
43966
  headers: { ...OFW_PROTOCOL_HEADERS },
43925
- redirect: "manual"
43967
+ redirect: "manual",
43968
+ // Honour a caller who has given up (mcp-utils `cancel`). A sign-in
43969
+ // nobody is waiting for should not keep hitting OFW, which counts
43970
+ // failed attempts against the account.
43971
+ signal: currentCallSignal()
43926
43972
  });
43927
43973
  const sessionCookie = initResponse.headers.getSetCookie().map((c) => c.split(";")[0]).join("; ");
43928
43974
  const loginUrl = `${BASE_URL}/ofw/login`;
43929
43975
  assertOfwUrl(loginUrl);
43930
43976
  const response = await fetch(loginUrl, {
43931
43977
  method: "POST",
43978
+ signal: currentCallSignal(),
43932
43979
  headers: {
43933
43980
  ...OFW_PROTOCOL_HEADERS,
43934
43981
  Accept: "application/json",
@@ -43965,7 +44012,7 @@ async function loginWithPassword(username, password) {
43965
44012
  // package.json
43966
44013
  var package_default = {
43967
44014
  name: "ofw-mcp",
43968
- version: "2.18.0",
44015
+ version: "2.19.0",
43969
44016
  license: "MIT",
43970
44017
  mcpName: "io.github.chrischall/ofw-mcp",
43971
44018
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -43999,7 +44046,7 @@ var package_default = {
43999
44046
  typecheck: "tsc -p tsconfig.json --noEmit"
44000
44047
  },
44001
44048
  dependencies: {
44002
- "@chrischall/mcp-utils": "^1.0.0",
44049
+ "@chrischall/mcp-utils": "^2.0.0",
44003
44050
  "@fetchproxy/bootstrap": "^3.0.1",
44004
44051
  "@modelcontextprotocol/server": "^2.0.0",
44005
44052
  dotenv: "^17.4.2",
@@ -44278,7 +44325,15 @@ var OFWClient = class {
44278
44325
  response = await fetch(url2, {
44279
44326
  method,
44280
44327
  headers,
44281
- signal: ac.signal,
44328
+ // THE CALLER'S CANCELLATION, folded in with our timeout (mcp-utils
44329
+ // `cancel`). Until this the only thing that could stop an OFW
44330
+ // request was the timeout below, so a cancelled tool call held it
44331
+ // open for the full budget while the child burned the CPU
44332
+ // mcp-host meters it on. The timeout diagnosis below is unaffected
44333
+ // because it asks `ac.signal`, OUR controller — a caller's abort
44334
+ // falls through to the generic path rather than being reported as
44335
+ // OFW being slow.
44336
+ signal: withAmbientCancellation(ac.signal),
44282
44337
  ...body !== void 0 ? { body: isFormData ? body : JSON.stringify(body) } : {}
44283
44338
  });
44284
44339
  } catch (err) {
@@ -48952,7 +49007,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
48952
49007
  var nodeAttachmentIO = new NodeAttachmentIO();
48953
49008
  await runMcp({
48954
49009
  name: "ofw",
48955
- version: "2.18.0",
49010
+ version: "2.19.0",
48956
49011
  // x-release-please-version
48957
49012
  deps: client,
48958
49013
  tools: [
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { loadDotenvSafely, parseBoolEnv, redactSecrets } from '@chrischall/mcp-utils';
1
+ import { loadDotenvSafely, parseBoolEnv, redactSecrets, withAmbientCancellation } from '@chrischall/mcp-utils';
2
2
  import { TokenManager } from '@chrischall/mcp-utils/session';
3
3
  import { dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
@@ -190,7 +190,15 @@ export class OFWClient {
190
190
  response = await fetch(url, {
191
191
  method,
192
192
  headers,
193
- signal: ac.signal,
193
+ // THE CALLER'S CANCELLATION, folded in with our timeout (mcp-utils
194
+ // `cancel`). Until this the only thing that could stop an OFW
195
+ // request was the timeout below, so a cancelled tool call held it
196
+ // open for the full budget while the child burned the CPU
197
+ // mcp-host meters it on. The timeout diagnosis below is unaffected
198
+ // because it asks `ac.signal`, OUR controller — a caller's abort
199
+ // falls through to the generic path rather than being reported as
200
+ // OFW being slow.
201
+ signal: withAmbientCancellation(ac.signal),
194
202
  ...(body !== undefined ? { body: isFormData ? body : JSON.stringify(body) } : {}),
195
203
  });
196
204
  }
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ const nodeAttachmentIO = new NodeAttachmentIO();
36
36
  // always succeeds before any credential check runs.
37
37
  await runMcp({
38
38
  name: 'ofw',
39
- version: '2.18.0', // x-release-please-version
39
+ version: '2.19.0', // x-release-please-version
40
40
  deps: client,
41
41
  tools: [
42
42
  registerHealthcheckTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
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)",
@@ -34,7 +34,7 @@
34
34
  "typecheck": "tsc -p tsconfig.json --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@chrischall/mcp-utils": "^1.0.0",
37
+ "@chrischall/mcp-utils": "^2.0.0",
38
38
  "@fetchproxy/bootstrap": "^3.0.1",
39
39
  "@modelcontextprotocol/server": "^2.0.0",
40
40
  "dotenv": "^17.4.2",
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.18.0",
9
+ "version": "2.19.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.18.0",
14
+ "version": "2.19.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },