zola-mcp 1.3.2 → 1.4.1

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Zola wedding planning tools for Claude Code",
10
- "version": "1.3.2"
10
+ "version": "1.4.1"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Zola",
16
16
  "source": "./",
17
17
  "description": "Zola wedding planning tools for Claude — vendors, budget, guests, seating, events, registry, inquiries, and more via MCP",
18
- "version": "1.3.2",
18
+ "version": "1.4.1",
19
19
  "author": {
20
20
  "name": "Chris Chall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zola",
3
3
  "displayName": "Zola",
4
- "version": "1.3.2",
4
+ "version": "1.4.1",
5
5
  "description": "Zola wedding planning tools for Claude — vendors, budget, guests, seating, events, registry, inquiries, and more via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall",
package/dist/auth.js CHANGED
@@ -1,60 +1,63 @@
1
1
  // ────────────────────────────────────────────────────────────────────────────
2
- // Auth resolution — Pattern A template
2
+ // Auth resolution — createAuthResolver (shared skeleton from mcp-utils)
3
3
  // ────────────────────────────────────────────────────────────────────────────
4
4
  //
5
- // Mirrors the canonical "browser-bootstrap + Node-direct" shape from
6
- // ofw-mcp/src/auth.ts. Other MCPs in this family (resy-mcp, opentable-mcp,
7
- // signupgenius-mcp, …) use the same selector — keep the structure flat,
8
- // the path-selection explicit, and the error messages actionable.
5
+ // The canonical three-path "browser-bootstrap + Node-direct" resolver, now the
6
+ // shared `createAuthResolver` skeleton from `@chrischall/mcp-utils` (0.7.0+):
9
7
  //
10
- // THE THREE PATHS, in priority order:
8
+ // 1. Env-var credential ZOLA_REFRESH_TOKEN set → used directly. This is
9
+ // the ~1-year JWT that doubles as the `usr` cookie on zola.com. Reads go
10
+ // through the hardened `readEnvVar` (blank / 'undefined' / 'null' /
11
+ // unexpanded `${VAR}` treated as unset).
11
12
  //
12
- // 1. Env-var credential (existing behavior)
13
- // ZOLA_REFRESH_TOKEN set use it directly. This is the ~1-year JWT
14
- // that doubles as the `usr` cookie on zola.com. Legacy users keep
15
- // working without action.
13
+ // 2. fetchproxy fallback unless ZOLA_DISABLE_FETCHPROXY is truthy
14
+ // ('1'/'true'/'yes'/'on' via `parseBoolEnv` inside the resolver), lift
15
+ // the HttpOnly `usr` cookie out of the user's signed-in zola.com tab via
16
+ // `@fetchproxy/bootstrap` (one-shot WebSocket bridge; `chrome.cookies.get`
17
+ // sees HttpOnly cookies). All subsequent Zola API calls go direct to
18
+ // mobile-api.zola.com from Node — fetchproxy is NOT in the hot path.
19
+ // Bridge-down errors surface the FetchproxyBridgeDownError `.hint`
20
+ // verbatim (handled inside createAuthResolver since 0.7.0); a signed-out
21
+ // tab raises SessionNotAuthenticatedError naming Zola + zola.com.
16
22
  //
17
- // 2. fetchproxy fallback (new)
18
- // When no token is set, lift the user's session out of their
19
- // signed-in zola.com browser tab via the fetchproxy 0.3.0 extension.
20
- // The `@fetchproxy/bootstrap` helper spins up a one-shot WebSocket
21
- // bridge, asks the extension for the HttpOnly `usr` cookie via
22
- // `chrome.cookies.get`, then closes the bridge. From there, all
23
- // Zola API calls go out via plain Node `fetch()` to
24
- // mobile-api.zola.com — fetchproxy is NOT in the hot path.
23
+ // 3. Error nothing configured: an actionable message naming
24
+ // ZOLA_REFRESH_TOKEN, the browser sign-in fallback, and
25
+ // ZOLA_DISABLE_FETCHPROXY.
25
26
  //
26
- // Users opt out with ZOLA_DISABLE_FETCHPROXY=1 (anyone who wants the
27
- // old behavior of "fail loudly when creds are missing").
28
- //
29
- // 3. Error
30
- // Nothing to authenticate with. We throw a message that tells the
31
- // user exactly what to do: set the env var, OR install the extension
32
- // and sign in.
33
- //
34
- // Why fetchproxy is only a one-shot read:
35
- // The bootstrap call snapshots the `usr` cookie and returns. The MCP
36
- // then operates from Node with direct fetch — latency and reliability
37
- // are not coupled to the browser bridge for normal tool calls. The
38
- // captured refresh token is fed into the existing
39
- // `POST /v3/sessions/refresh` flow which mints 30-min session tokens
40
- // (also in pure Node).
41
- //
42
- // Testability:
43
- // - `@fetchproxy/bootstrap` is mocked at the module boundary in tests.
44
- // - This module exposes a single async `resolveRefreshToken()` that
45
- // returns the JWT plus the source — callers (the client) treat the
46
- // return value as opaque credentials.
27
+ // Testability: `@fetchproxy/bootstrap` is mocked at the module boundary in
28
+ // tests, exactly as before. This module still exposes a single async
29
+ // `resolveRefreshToken()` returning the JWT plus the source — callers (the
30
+ // client) treat the return value as opaque credentials.
47
31
  import { bootstrap } from '@fetchproxy/bootstrap';
48
- import { classifyBridgeError } from '@chrischall/mcp-utils/fetchproxy';
49
- import { readEnvVar } from '@chrischall/mcp-utils';
32
+ import { createAuthResolver } from '@chrischall/mcp-utils';
50
33
  import pkg from '../package.json' with { type: 'json' };
51
- /** True if the user has explicitly disabled the fetchproxy fallback. */
52
- function fetchproxyDisabled() {
53
- const raw = readEnvVar('ZOLA_DISABLE_FETCHPROXY');
54
- if (raw === undefined)
55
- return false;
56
- return ['1', 'true', 'yes', 'on'].includes(raw.toLowerCase());
57
- }
34
+ const resolveAuth = createAuthResolver({
35
+ envVar: 'ZOLA_REFRESH_TOKEN',
36
+ disableEnvVar: 'ZOLA_DISABLE_FETCHPROXY',
37
+ // The real bootstrap has a narrower opts type than the injected boundary;
38
+ // the cast keeps the heavy bridge dep out of the shared module's types.
39
+ bootstrap: bootstrap,
40
+ bootstrapOptions: {
41
+ serverName: pkg.name,
42
+ version: pkg.version,
43
+ // Zola serves www.zola.com (web app) and mobile-api.zola.com (API).
44
+ // The `usr` cookie lives on the web app's apex domain; the extension
45
+ // matches on suffix so listing the apex covers any subdomain.
46
+ domains: ['zola.com'],
47
+ declare: {
48
+ // `usr` is HttpOnly → invisible to page JS, but fetchproxy's
49
+ // read_cookies uses `chrome.cookies.get` which DOES see HttpOnly
50
+ // cookies. The value IS the ~1-year refresh JWT.
51
+ cookies: ['usr'],
52
+ localStorage: [],
53
+ sessionStorage: [],
54
+ captureHeaders: [],
55
+ },
56
+ },
57
+ parseTokens: (session) => session.cookies['usr'],
58
+ serviceName: 'Zola',
59
+ signInHost: 'zola.com',
60
+ });
58
61
  /**
59
62
  * Resolve the Zola refresh token using the three-path priority described
60
63
  * above. Throws with an actionable error message when no path succeeds.
@@ -64,58 +67,6 @@ function fetchproxyDisabled() {
64
67
  * logging / future cache-keying only.
65
68
  */
66
69
  export async function resolveRefreshToken() {
67
- // ── Path 1: env-var refresh token (unchanged from pre-fetchproxy behavior).
68
- const envToken = readEnvVar('ZOLA_REFRESH_TOKEN');
69
- if (envToken) {
70
- return { token: envToken, source: 'env' };
71
- }
72
- // ── Path 2: fetchproxy fallback (new).
73
- if (!fetchproxyDisabled()) {
74
- try {
75
- const session = await bootstrap({
76
- serverName: pkg.name,
77
- version: pkg.version,
78
- // Zola serves www.zola.com (web app) and mobile-api.zola.com (API).
79
- // The `usr` cookie lives on the web app's apex domain; the extension
80
- // matches on suffix so listing the apex covers any subdomain.
81
- domains: ['zola.com'],
82
- declare: {
83
- // `usr` is HttpOnly → invisible to page JS, but fetchproxy 0.3.0's
84
- // read_cookies uses `chrome.cookies.get` which DOES see HttpOnly
85
- // cookies. The value IS the ~1-year refresh JWT.
86
- cookies: ['usr'],
87
- localStorage: [],
88
- sessionStorage: [],
89
- captureHeaders: [],
90
- },
91
- });
92
- const token = session.cookies['usr'];
93
- if (!token) {
94
- throw new Error('zola: no `usr` cookie found. ' +
95
- 'Sign into zola.com in your browser (with the fetchproxy extension installed) and retry.');
96
- }
97
- return { token, source: 'fetchproxy' };
98
- }
99
- catch (e) {
100
- // 0.8.0+ typed-error discrimination. The fetchproxy server already
101
- // retries once on SW eviction (bridgeReviveDelayMs=2000 default), so
102
- // a thrown FetchproxyBridgeDownError means the retry also failed —
103
- // the extension's service worker is genuinely down and the user
104
- // needs to wake it. The `.hint` is the actionable copy
105
- // ("click the extension toolbar icon...") that we'd otherwise have
106
- // to hand-write here. Surface it verbatim so users in path 2 get
107
- // the same self-service guidance as path 3.
108
- if (classifyBridgeError(e) === 'bridge_down') {
109
- const downErr = e;
110
- throw new Error(`Zola auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
111
- }
112
- const msg = e instanceof Error ? e.message : String(e);
113
- throw new Error(`Zola auth: no ZOLA_REFRESH_TOKEN set, and fetchproxy fallback failed: ${msg}`);
114
- }
115
- }
116
- // ── Path 3: nothing configured. Surface both fixes side-by-side so the
117
- // user can pick whichever fits their setup.
118
- throw new Error('Zola auth: set ZOLA_REFRESH_TOKEN, ' +
119
- 'or install the fetchproxy extension and sign into zola.com ' +
120
- '(unset ZOLA_DISABLE_FETCHPROXY if it is set).');
70
+ const { credential, source } = await resolveAuth();
71
+ return { token: credential, source };
121
72
  }