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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +24 -1
- package/dist/bundle.js +479 -49
- package/dist/client.js +4 -1
- package/dist/config.js +37 -0
- package/dist/index.js +13 -9
- package/dist/tool-surface.js +14 -0
- package/dist/tools/_shared.js +5 -1
- package/dist/tools/expenses.js +563 -56
- package/package.json +1 -1
- package/server.json +14 -2
- package/skills/ofw/SKILL.md +10 -3
- package/skills/ofw-fpx/references/requests.md +58 -4
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.
|
|
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
|
+
}
|
package/dist/tools/_shared.js
CHANGED
|
@@ -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}
|
|
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) {
|