ofw-mcp 2.0.13 → 2.0.14
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 +20 -9
- package/dist/auth-password.js +57 -0
- package/dist/auth.js +135 -0
- package/dist/bundle.js +9109 -3284
- package/dist/client.js +12 -59
- package/dist/config.js +18 -8
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/server.json +2 -2
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import { resolveAuth } from './auth.js';
|
|
3
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb bundle)
|
|
4
5
|
try {
|
|
5
6
|
const { config } = await import('dotenv');
|
|
@@ -9,24 +10,6 @@ try {
|
|
|
9
10
|
catch {
|
|
10
11
|
// not available — rely on process.env (mcpb sets credentials via mcp_config.env)
|
|
11
12
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Read an env var, trim whitespace, and treat as unset if blank or if the value
|
|
14
|
-
* looks like an unsubstituted shell placeholder (e.g. `${FOO}`) — defends
|
|
15
|
-
* against MCP hosts that pass .mcp.json env blocks through unexpanded.
|
|
16
|
-
*/
|
|
17
|
-
function readVar(key) {
|
|
18
|
-
const raw = process.env[key];
|
|
19
|
-
if (typeof raw !== 'string')
|
|
20
|
-
return undefined;
|
|
21
|
-
const trimmed = raw.trim();
|
|
22
|
-
if (trimmed.length === 0)
|
|
23
|
-
return undefined;
|
|
24
|
-
if (trimmed === 'undefined' || trimmed === 'null')
|
|
25
|
-
return undefined;
|
|
26
|
-
if (/^\$\{[^}]*\}$/.test(trimmed))
|
|
27
|
-
return undefined;
|
|
28
|
-
return trimmed;
|
|
29
|
-
}
|
|
30
13
|
const BASE_URL = 'https://ofw.ourfamilywizard.com';
|
|
31
14
|
const OFW_PROTOCOL_HEADERS = {
|
|
32
15
|
'ofw-client': 'WebApplication',
|
|
@@ -107,48 +90,18 @@ export class OFWClient {
|
|
|
107
90
|
return;
|
|
108
91
|
await this.login();
|
|
109
92
|
}
|
|
93
|
+
// Auth resolution is delegated to `./auth.ts`. This client doesn't care
|
|
94
|
+
// whether the token came from a password POST or from a one-shot
|
|
95
|
+
// fetchproxy session-snapshot — it just consumes the result.
|
|
96
|
+
//
|
|
97
|
+
// If `expiresAt` is missing (the fetchproxy path on a tab whose
|
|
98
|
+
// browser didn't persist tokenExpiry), we fall back to the same 6h
|
|
99
|
+
// estimate the password path uses. The 401-replay path covers us if
|
|
100
|
+
// the estimate is wrong.
|
|
110
101
|
async login() {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw new Error('OFW_USERNAME and OFW_PASSWORD must be set');
|
|
115
|
-
}
|
|
116
|
-
// Spring Security requires a SESSION cookie before accepting the login POST.
|
|
117
|
-
// GET /ofw/login.form with redirect:manual to capture the Set-Cookie from the 303 response.
|
|
118
|
-
const initResponse = await fetch(`${BASE_URL}/ofw/login.form`, {
|
|
119
|
-
headers: { ...OFW_PROTOCOL_HEADERS },
|
|
120
|
-
redirect: 'manual',
|
|
121
|
-
});
|
|
122
|
-
// Extract just the SESSION=value part (strip attributes like Path, Secure, etc.)
|
|
123
|
-
const setCookie = initResponse.headers.get('set-cookie') ?? '';
|
|
124
|
-
const sessionCookie = setCookie.split(';')[0];
|
|
125
|
-
const response = await fetch(`${BASE_URL}/ofw/login`, {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: {
|
|
128
|
-
...OFW_PROTOCOL_HEADERS,
|
|
129
|
-
Accept: 'application/json',
|
|
130
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
131
|
-
...(sessionCookie ? { Cookie: sessionCookie } : {}),
|
|
132
|
-
},
|
|
133
|
-
body: new URLSearchParams({
|
|
134
|
-
submit: 'Sign In',
|
|
135
|
-
_eventId: 'submit',
|
|
136
|
-
username,
|
|
137
|
-
password,
|
|
138
|
-
}).toString(),
|
|
139
|
-
});
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
throw new Error(`OFW login failed: ${response.status} ${response.statusText}`);
|
|
142
|
-
}
|
|
143
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
144
|
-
if (!contentType.includes('application/json')) {
|
|
145
|
-
const body = await response.text();
|
|
146
|
-
throw new Error(`OFW login returned unexpected response (${contentType}): ${body.substring(0, 200)}`);
|
|
147
|
-
}
|
|
148
|
-
const data = (await response.json());
|
|
149
|
-
this.token = data.auth;
|
|
150
|
-
// Token expiry not returned by login endpoint; use 6h as a safe default
|
|
151
|
-
this.tokenExpiry = new Date(Date.now() + 6 * 60 * 60 * 1000);
|
|
102
|
+
const { token, expiresAt } = await resolveAuth();
|
|
103
|
+
this.token = token;
|
|
104
|
+
this.tokenExpiry = expiresAt ?? new Date(Date.now() + 6 * 60 * 60 * 1000);
|
|
152
105
|
}
|
|
153
106
|
isTokenExpiredSoon() {
|
|
154
107
|
if (!this.token || !this.tokenExpiry)
|
package/dist/config.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
// Cache identity drives the per-user SQLite DB filename. Order of preference:
|
|
5
|
+
// 1. OFW_CACHE_IDENTITY — explicit override for users who want to label the
|
|
6
|
+
// cache themselves (e.g. when authing via fetchproxy and OFW_USERNAME is
|
|
7
|
+
// not set).
|
|
8
|
+
// 2. OFW_USERNAME — legacy path; existing users keep their existing DB.
|
|
9
|
+
// 3. "_default" — fallback for fetchproxy-only setups where neither is set.
|
|
10
|
+
// Single-user installs are fine on this; multi-account users should set
|
|
11
|
+
// OFW_CACHE_IDENTITY explicitly so their caches don't collide.
|
|
12
|
+
function readCacheIdentity() {
|
|
13
|
+
const explicit = process.env.OFW_CACHE_IDENTITY;
|
|
14
|
+
if (typeof explicit === 'string' && explicit.trim().length > 0)
|
|
15
|
+
return explicit.trim();
|
|
16
|
+
const username = process.env.OFW_USERNAME;
|
|
17
|
+
if (typeof username === 'string' && username.trim().length > 0)
|
|
18
|
+
return username.trim();
|
|
19
|
+
return '_default';
|
|
10
20
|
}
|
|
11
21
|
export function getCacheDir() {
|
|
12
22
|
const override = process.env.OFW_CACHE_DIR;
|
|
@@ -15,8 +25,8 @@ export function getCacheDir() {
|
|
|
15
25
|
return join(homedir(), '.cache', 'ofw-mcp');
|
|
16
26
|
}
|
|
17
27
|
export function getCacheDbPath() {
|
|
18
|
-
const
|
|
19
|
-
const hash = createHash('sha256').update(
|
|
28
|
+
const identity = readCacheIdentity();
|
|
29
|
+
const hash = createHash('sha256').update(identity).digest('hex').slice(0, 16);
|
|
20
30
|
return join(getCacheDir(), `${hash}.db`);
|
|
21
31
|
}
|
|
22
32
|
export function getAttachmentsDir() {
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { registerMessageTools } from './tools/messages.js';
|
|
|
17
17
|
import { registerCalendarTools } from './tools/calendar.js';
|
|
18
18
|
import { registerExpenseTools } from './tools/expenses.js';
|
|
19
19
|
import { registerJournalTools } from './tools/journal.js';
|
|
20
|
-
const server = new McpServer({ name: 'ofw', version: '2.0.
|
|
20
|
+
const server = new McpServer({ name: 'ofw', version: '2.0.14' });
|
|
21
21
|
registerUserTools(server, client);
|
|
22
22
|
registerMessageTools(server, client);
|
|
23
23
|
registerCalendarTools(server, client);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"mcpName": "io.github.chrischall/ofw-mcp",
|
|
5
|
-
"description": "OurFamilyWizard MCP server for Claude
|
|
5
|
+
"description": "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && npm run bundle",
|
|
24
|
-
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --outfile=dist/bundle.js",
|
|
24
|
+
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from \"module\"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js",
|
|
25
25
|
"dev": "node --env-file=.env dist/index.js",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@fetchproxy/bootstrap": "^0.4.2",
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
32
|
"dotenv": "^17.4.0",
|
|
32
33
|
"zod": "^4.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.0.
|
|
9
|
+
"version": "2.0.14",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.0.
|
|
14
|
+
"version": "2.0.14",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|