ofw-mcp 2.19.5 → 2.20.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.
package/dist/config.js CHANGED
@@ -51,6 +51,43 @@ export function getUploadDir() {
51
51
  return override.trim();
52
52
  return getAttachmentsDir();
53
53
  }
54
+ /**
55
+ * Structural surface restrictions for deployments dedicated to reimbursement
56
+ * automation.
57
+ *
58
+ * OFW_EXPENSE_ONLY=true removes every non-expense registrar except the
59
+ * healthcheck. Message, calendar, journal and profile/dashboard tools simply do
60
+ * not exist in the server's tools/list response.
61
+ *
62
+ * OFW_EXPENSE_UPLOAD_ONLY=true is stricter and implies OFW_EXPENSE_ONLY: only
63
+ * ofw_upload_expense_pdf + ofw_create_expense + ofw_update_expense (plus
64
+ * ofw_healthcheck) register.
65
+ * Expense reads such as totals/list are omitted as well.
66
+ *
67
+ * These are startup-time structural controls. They cannot be raised by a tool
68
+ * argument, prompt injection, or host permission setting.
69
+ *
70
+ * Unrecognized non-empty values fail CLOSED to true. A typo in a restriction
71
+ * flag must never silently widen the server surface.
72
+ */
73
+ function restrictiveBoolEnv(name) {
74
+ const raw = process.env[name];
75
+ if (typeof raw !== 'string' || raw.trim().length === 0)
76
+ return false;
77
+ const value = raw.trim().toLowerCase();
78
+ if (['1', 'true', 'yes', 'on'].includes(value))
79
+ return true;
80
+ if (['0', 'false', 'no', 'off'].includes(value))
81
+ return false;
82
+ console.error(`[ofw-mcp] Unrecognized ${name} "${raw.trim()}" — failing closed to "true" (restriction enabled). Valid values: true, false.`);
83
+ return true;
84
+ }
85
+ export function getExpenseUploadOnly() {
86
+ return restrictiveBoolEnv('OFW_EXPENSE_UPLOAD_ONLY');
87
+ }
88
+ export function getExpenseOnly() {
89
+ return getExpenseUploadOnly() || restrictiveBoolEnv('OFW_EXPENSE_ONLY');
90
+ }
54
91
  /**
55
92
  * Gate for write-tool registration, read at registration time (startup).
56
93
  *
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import { registerExpenseTools } from './tools/expenses.js';
19
19
  import { registerJournalTools } from './tools/journal.js';
20
20
  import { OFWCache } from './cache/node.js';
21
21
  import { getCacheDbPath } from './config.js';
22
+ import { selectToolRegistrars } from './tool-surface.js';
22
23
  import { NodeAttachmentIO } from './tools/attachments.js';
23
24
  // The stdio server backs the message cache with a local `node:sqlite` file,
24
25
  // opened lazily on first use (so the server still boots and answers the host's
@@ -34,17 +35,20 @@ const nodeAttachmentIO = new NodeAttachmentIO();
34
35
  // is preserved: `client` is constructed at module load in ./client.js (auth is
35
36
  // resolved lazily on the first tool call), so the host's initial tools/list
36
37
  // always succeeds before any credential check runs.
38
+ const expenseRegistrar = (server, deps) => registerExpenseTools(server, deps, nodeAttachmentIO);
39
+ const messageRegistrar = (server, deps) => registerMessageTools(server, deps, nodeCacheProvider, nodeAttachmentIO);
40
+ const tools = selectToolRegistrars({
41
+ healthcheck: registerHealthcheckTools,
42
+ user: registerUserTools,
43
+ messages: messageRegistrar,
44
+ calendar: registerCalendarTools,
45
+ expenses: expenseRegistrar,
46
+ journal: registerJournalTools,
47
+ });
37
48
  await runMcp({
38
49
  name: 'ofw',
39
- version: '2.19.5', // x-release-please-version
50
+ version: '2.20.0', // x-release-please-version
40
51
  deps: client,
41
- tools: [
42
- registerHealthcheckTools,
43
- registerUserTools,
44
- (server, deps) => registerMessageTools(server, deps, nodeCacheProvider, nodeAttachmentIO),
45
- registerCalendarTools,
46
- registerExpenseTools,
47
- registerJournalTools,
48
- ],
52
+ tools,
49
53
  banner: '[ofw-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
50
54
  });
@@ -0,0 +1,14 @@
1
+ import { getExpenseOnly } from './config.js';
2
+ /**
3
+ * Select the registrars exposed by this deployment.
4
+ *
5
+ * Expense-only mode is structural: omitted registrars are never handed to
6
+ * runMcp, so their tools cannot appear in tools/list or be invoked by name.
7
+ * OFW_EXPENSE_UPLOAD_ONLY implies this same registrar restriction and the
8
+ * expense registrar itself removes its read tools.
9
+ */
10
+ export function selectToolRegistrars(r) {
11
+ if (getExpenseOnly())
12
+ return [r.healthcheck, r.expenses];
13
+ return [r.healthcheck, r.user, r.messages, r.calendar, r.expenses, r.journal];
14
+ }
@@ -292,11 +292,15 @@ export async function requestWrite(client, method, path, body) {
292
292
  * may have landed and names how to check before any retry.
293
293
  */
294
294
  export function unconfirmedWriteResponse(e, opts) {
295
+ // The web app is the fallback for a checkWith that names a tool. A checkWith
296
+ // that already sends the caller to the web app (a deployment where the list
297
+ // tool is not registered) needs no second mention of it.
298
+ const fallback = /ourfamilywizard\.com/i.test(opts.checkWith) ? '' : ' (or on ourfamilywizard.com)';
295
299
  return jsonErrorResponse({
296
300
  result: opts.result,
297
301
  mayHaveLanded: true,
298
302
  reason: `The request to ${opts.what} failed without a definitive answer from OFW: ${e.message}. It MAY HAVE BEEN APPLIED on OurFamilyWizard.`,
299
- remedy: `Do NOT retry blindly — a second attempt can put a duplicate on the co-parent-visible record. First check with ${opts.checkWith} (or on ourfamilywizard.com), and retry only once you have confirmed it did not land.`,
303
+ remedy: `Do NOT retry blindly — a second attempt can put a duplicate on the co-parent-visible record. First check with ${opts.checkWith}${fallback}, and retry only once you have confirmed it did not land.`,
300
304
  });
301
305
  }
302
306
  export async function postMessageAndRefetch(client, payload, detailSchema, ctx) {