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/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 username = readVar('OFW_USERNAME');
112
- const password = readVar('OFW_PASSWORD');
113
- if (!username || !password) {
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
- function readUsername() {
5
- const raw = process.env.OFW_USERNAME;
6
- if (typeof raw !== 'string' || raw.trim().length === 0) {
7
- throw new Error('OFW_USERNAME must be set to derive cache path');
8
- }
9
- return raw.trim();
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 username = readUsername();
19
- const hash = createHash('sha256').update(username).digest('hex').slice(0, 16);
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.13' });
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.13",
3
+ "version": "2.0.14",
4
4
  "mcpName": "io.github.chrischall/ofw-mcp",
5
- "description": "OurFamilyWizard MCP server for Claude developed and maintained by AI (Claude Code)",
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.13",
9
+ "version": "2.0.14",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.0.13",
14
+ "version": "2.0.14",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },