ofw-mcp 2.20.2 → 2.20.3

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.20.2"
9
+ "version": "2.20.3"
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.20.2",
17
+ "version": "2.20.3",
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.20.2",
4
+ "version": "2.20.3",
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
@@ -227,13 +227,14 @@ Every list read also carries an explicit **`complete`** boolean describing the *
227
227
 
228
228
  ### Server-side confirmation (`MCP_CONFIRM_MODE`)
229
229
 
230
- 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. 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.
230
+ 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.
231
231
 
232
232
  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.
233
233
 
234
234
  | Variable | Default | |
235
235
  |---|---|---|
236
236
  | `MCP_CONFIRM_MODE` | `ask-user` | What 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`. |
237
+ | `MCP_CONFIRM_ELICITATION` | `on` | `off` 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. |
237
238
  | `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
238
239
  | `MCP_CONFIRM_SECRET` | random per process | Signing 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. |
239
240
 
package/dist/bundle.js CHANGED
@@ -38459,6 +38459,7 @@ function escapeKey(key) {
38459
38459
  var DEFAULT_REQUEST_KEY = "confirmation";
38460
38460
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
38461
38461
  var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
38462
+ var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
38462
38463
  var STATE_PREFIX = "mcpu.confirm.v1.";
38463
38464
  function bindingKey(binding) {
38464
38465
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
@@ -38531,6 +38532,15 @@ ${JSON.stringify(preview, null, 2)}`,
38531
38532
  };
38532
38533
  const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
38533
38534
  const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
38535
+ if (options.elicitation === false) {
38536
+ return textResult({
38537
+ confirmed: false,
38538
+ dispatched: false,
38539
+ action: options.action,
38540
+ reason: "confirmation-unsupported",
38541
+ note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
38542
+ });
38543
+ }
38534
38544
  if (response.kind === "missing") {
38535
38545
  if (callerAcceptsFormElicitation(ctx) === false) {
38536
38546
  return textResult({
@@ -38725,7 +38735,7 @@ async function tokenConfirmation(action, fb) {
38725
38735
  }
38726
38736
  async function requireConfirmationWithFallback(ctx, options) {
38727
38737
  const { tokenFallback, ...confirmation } = options;
38728
- if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
38738
+ if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
38729
38739
  return tokenConfirmation(options.action, tokenFallback);
38730
38740
  }
38731
38741
  return requireConfirmation(ctx, confirmation);
@@ -38887,6 +38897,20 @@ function readConfirmMode(env = process.env) {
38887
38897
  }
38888
38898
  return "refuse";
38889
38899
  }
38900
+ function readConfirmElicitation(env = process.env) {
38901
+ const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
38902
+ if (!raw || raw === "on")
38903
+ return "on";
38904
+ if (raw === "off")
38905
+ return "off";
38906
+ const key = `elicitation:${raw}`;
38907
+ if (!warned.has(key)) {
38908
+ warned.add(key);
38909
+ process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
38910
+ `);
38911
+ }
38912
+ return "on";
38913
+ }
38890
38914
  function readConfirmTtl(env) {
38891
38915
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
38892
38916
  if (!raw)
@@ -38931,6 +38955,7 @@ function spentTokenStoreFromEnv(env = process.env) {
38931
38955
  }
38932
38956
  var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
38933
38957
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
38958
+ var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
38934
38959
  var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
38935
38960
  function boundArgs(args) {
38936
38961
  if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
@@ -38944,10 +38969,13 @@ function confirmationFromEnv(options) {
38944
38969
  const spent = callerSpent ?? spentTokenStoreFromEnv(env);
38945
38970
  const mode = readConfirmMode(env);
38946
38971
  const ttlSeconds = readConfirmTtl(env);
38972
+ const elicitationOff = readConfirmElicitation(env) === "off";
38947
38973
  const bound = args === void 0 ? void 0 : boundArgs(args);
38948
- const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
38974
+ const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
38975
+ const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
38949
38976
  if (mode === "refuse" || ttlSeconds === void 0) {
38950
- const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
38977
+ const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
38978
+ const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
38951
38979
  return {
38952
38980
  ...confirmation,
38953
38981
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
@@ -40389,6 +40417,17 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
40389
40417
  ]);
40390
40418
  var ACCOUNT_KEY_FRAME = "account-key";
40391
40419
  var ACCOUNT_ATTEST_FRAME = "account-attest";
40420
+ var BRIDGE_ROLE_FRAME = "bridge-role";
40421
+ var BRIDGE_SERVE_FRAME = "bridge-serve";
40422
+ var ROOM_PING_FRAME = "room-ping";
40423
+ var ROOM_PONG_FRAME = "room-pong";
40424
+ var BRIDGE_ROLE_LABEL_MAX = 64;
40425
+ var ROOM_FRAME_ACCEPTS = Object.freeze({
40426
+ [BRIDGE_ROLE_FRAME]: BRIDGE_ROLE_FRAME,
40427
+ [BRIDGE_SERVE_FRAME]: BRIDGE_SERVE_FRAME,
40428
+ [ROOM_PING_FRAME]: ROOM_PING_FRAME,
40429
+ [ROOM_PONG_FRAME]: ROOM_PING_FRAME
40430
+ });
40392
40431
 
40393
40432
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
40394
40433
  var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
@@ -41054,6 +41093,12 @@ function validateFrame(raw) {
41054
41093
  return validateAccountKey(raw);
41055
41094
  if (t === ACCOUNT_ATTEST_FRAME)
41056
41095
  return validateAccountAttest(raw);
41096
+ if (t === BRIDGE_ROLE_FRAME)
41097
+ return validateBridgeRole(raw);
41098
+ if (t === BRIDGE_SERVE_FRAME || t === ROOM_PING_FRAME || t === ROOM_PONG_FRAME) {
41099
+ assertExactFields(raw, TYPE_ONLY_FIELDS, t);
41100
+ return { type: t };
41101
+ }
41057
41102
  throw new ProtocolError3(`unknown frame type: ${String(t)}`);
41058
41103
  }
41059
41104
  function validateHello(raw) {
@@ -41379,6 +41424,43 @@ function validateAccountAttest(raw) {
41379
41424
  sig: raw.sig
41380
41425
  };
41381
41426
  }
41427
+ var TYPE_ONLY_FIELDS = ["type"];
41428
+ var BRIDGE_ROLE_FIELDS = ["type", "role", "canServe", "serving"];
41429
+ var BRIDGE_ROLE_SERVING_FIELDS = ["label", "since"];
41430
+ function validateBridgeRole(raw) {
41431
+ const L = BRIDGE_ROLE_FRAME;
41432
+ const role = raw.role;
41433
+ if (role !== "serving" && role !== "standby") {
41434
+ throw new ProtocolError3(`${L}.role: must be one of serving, standby`);
41435
+ }
41436
+ const canServe = raw.canServe;
41437
+ assertBoolean(canServe, `${L}.canServe`);
41438
+ assertExactFields(raw, BRIDGE_ROLE_FIELDS, L);
41439
+ const serving = raw.serving;
41440
+ if (role === "serving") {
41441
+ if (serving !== void 0) {
41442
+ throw new ProtocolError3(`${L}.serving: must be absent when role is serving`);
41443
+ }
41444
+ return { type: BRIDGE_ROLE_FRAME, role, canServe };
41445
+ }
41446
+ if (serving === void 0) {
41447
+ throw new ProtocolError3(`${L}.serving: required when role is standby`);
41448
+ }
41449
+ assertObject(serving, `${L}.serving`);
41450
+ const label = serving.label;
41451
+ assertDisplayString(label, `${L}.serving.label`, 1, BRIDGE_ROLE_LABEL_MAX);
41452
+ const since = serving.since;
41453
+ if (typeof since !== "number" || !Number.isSafeInteger(since) || since < 0) {
41454
+ throw new ProtocolError3(`${L}.serving.since: expected non-negative safe integer (Unix ms)`);
41455
+ }
41456
+ assertExactFields(serving, BRIDGE_ROLE_SERVING_FIELDS, `${L}.serving`);
41457
+ return {
41458
+ type: BRIDGE_ROLE_FRAME,
41459
+ role,
41460
+ canServe,
41461
+ serving: { label, since }
41462
+ };
41463
+ }
41382
41464
  function validateInnerFrame(raw) {
41383
41465
  assertObject(raw, "inner");
41384
41466
  const t = raw.type;
@@ -46581,7 +46663,7 @@ async function loginWithPassword(username, password) {
46581
46663
  // package.json
46582
46664
  var package_default = {
46583
46665
  name: "ofw-mcp",
46584
- version: "2.20.2",
46666
+ version: "2.20.3",
46585
46667
  license: "MIT",
46586
46668
  mcpName: "io.github.chrischall/ofw-mcp",
46587
46669
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -46615,8 +46697,8 @@ var package_default = {
46615
46697
  typecheck: "tsc -p tsconfig.json --noEmit"
46616
46698
  },
46617
46699
  dependencies: {
46618
- "@chrischall/mcp-utils": "^2.14.0",
46619
- "@fetchproxy/bootstrap": "^3.4.1",
46700
+ "@chrischall/mcp-utils": "^2.15.0",
46701
+ "@fetchproxy/bootstrap": "^3.6.0",
46620
46702
  "@modelcontextprotocol/server": "^2.3.0",
46621
46703
  dotenv: "^18.0.0",
46622
46704
  zod: "^4.6.5"
@@ -52499,7 +52581,7 @@ var tools = selectToolRegistrars({
52499
52581
  });
52500
52582
  await runMcp({
52501
52583
  name: "ofw",
52502
- version: "2.20.2",
52584
+ version: "2.20.3",
52503
52585
  // x-release-please-version
52504
52586
  deps: client,
52505
52587
  tools,
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ const tools = selectToolRegistrars({
47
47
  });
48
48
  await runMcp({
49
49
  name: 'ofw',
50
- version: '2.20.2', // x-release-please-version
50
+ version: '2.20.3', // x-release-please-version
51
51
  deps: client,
52
52
  tools,
53
53
  banner: '[ofw-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
package/mint.yaml CHANGED
@@ -39,6 +39,13 @@ env:
39
39
  chat before calling again with it. "auto": the model may use the token
40
40
  after reviewing the preview itself. "refuse": such writes are refused.
41
41
  Unrecognised values are treated as "refuse".
42
+ - name: MCP_CONFIRM_ELICITATION
43
+ required: false
44
+ help: >-
45
+ "off" never shows a confirmation prompt, so every client gets the
46
+ MCP_CONFIRM_MODE path. Set it for a client that claims to support
47
+ prompts but never shows one (the gated write hangs, e.g. opencode
48
+ 2.0.x). Default "on"; any other value stays "on" with a stderr warning.
42
49
  - name: MCP_CONFIRM_TTL_SECONDS
43
50
  required: false
44
51
  help: >-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofw-mcp",
3
- "version": "2.20.2",
3
+ "version": "2.20.3",
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,8 +34,8 @@
34
34
  "typecheck": "tsc -p tsconfig.json --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@chrischall/mcp-utils": "^2.14.0",
38
- "@fetchproxy/bootstrap": "^3.4.1",
37
+ "@chrischall/mcp-utils": "^2.15.0",
38
+ "@fetchproxy/bootstrap": "^3.6.0",
39
39
  "@modelcontextprotocol/server": "^2.3.0",
40
40
  "dotenv": "^18.0.0",
41
41
  "zod": "^4.6.5"
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.20.2",
9
+ "version": "2.20.3",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.20.2",
14
+ "version": "2.20.3",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },