zola-mcp 1.1.2 → 1.2.3

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.1.2"
10
+ "version": "1.2.3"
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.1.2",
18
+ "version": "1.2.3",
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.1.2",
4
+ "version": "1.2.3",
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/README.md CHANGED
@@ -23,7 +23,29 @@ Ask Claude things like:
23
23
  - [Claude Desktop](https://claude.ai/download) or [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
24
24
  - [Node.js](https://nodejs.org) 20.6 or later
25
25
  - A [Zola](https://www.zola.com) account
26
- - [Google Chrome](https://www.google.com/chrome/) — used once for the scripted auth flow (optional; you can copy the cookie manually instead)
26
+ - For the no-env-var path: the [fetchproxy 0.3.0 Chrome / Safari extension](https://github.com/chrischall/fetchproxy)
27
+
28
+ ## Acknowledgement of Terms
29
+
30
+ By using this MCP server, you acknowledge and agree to the following:
31
+
32
+ **1. This server accesses your own Zola account.** Auth happens via your own credentials. It does not — and cannot — access anyone else's wedding website, registry, or guest list.
33
+
34
+ **2. [Zola's Terms of Use](https://www.zola.com/terms) govern your use of this server**, just as they govern your direct use of zola.com. The clauses most relevant here:
35
+
36
+ > [You may not use] any hardware or software intended to surreptitiously intercept or otherwise obtain any information… including but not limited to the use of any "scraping" or other data mining techniques, robots or similar data gathering and extraction tools.
37
+
38
+ And, critically, on agent-acting-as-you: *"You are responsible for maintaining the confidentiality of your account and password… You accept full responsibility for all activities that occur under your account and password, **even if such actions are undertaken by your Authorized Agent or other third party**."*
39
+
40
+ You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server. Zola's ToU is explicit: this MCP acting as your Authorized Agent counts as you.
41
+
42
+ **3. Personal, non-commercial use only.** This project is not affiliated with, endorsed by, sponsored by, or in partnership with Zola, Inc. It is a personal automation tool for one couple to manage their own wedding website, registry, vendor research, and guest list. Do not use it to bulk-extract Zola's vendor directory, scrape registries, or compete with Zola.
43
+
44
+ **4. Stability is not guaranteed.** This server may call internal Zola endpoints that change without notice. It may break.
45
+
46
+ **5. You accept full responsibility** for any consequences of using this server in connection with your Zola account — rate limiting, account warnings, suspension, or any enforcement action. Per Zola's ToU, anything this MCP does under your account is your action — review guest list edits, registry changes, and inquiries before confirming. If Zola objects to your use, stop using this server.
47
+
48
+ This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede Zola's actual ToU.
27
49
 
28
50
  ## Installation
29
51
 
@@ -89,16 +111,17 @@ Add to Claude Desktop config:
89
111
 
90
112
  ### Getting your refresh token
91
113
 
92
- Sign in to zola.com and capture the `usr` cookie — a ~1-year JWT that doubles as the refresh token. The token lasts ~1 year; this is a one-time setup.
114
+ You have two options. Both produce the same `usr` cookie value — a ~1-year JWT that doubles as the refresh token.
93
115
 
94
- #### Option A — scripted (recommended)
116
+ #### Option A — fetchproxy extension (recommended)
95
117
 
96
- ```bash
97
- npm run auth # prints the token to the console
98
- npm run auth -- .env # writes ZOLA_REFRESH_TOKEN=<token> to .env
99
- ```
118
+ 1. Install the [fetchproxy 0.3.0 extension](https://github.com/chrischall/fetchproxy) (Chrome Web Store or Safari `.dmg`).
119
+ 2. Sign in at [zola.com/account/login](https://www.zola.com/account/login) in that browser.
120
+ 3. Leave `ZOLA_REFRESH_TOKEN` **unset** in your Claude config.
121
+
122
+ On the first tool call, the MCP asks the extension for the HttpOnly `usr` cookie via `chrome.cookies.get`, then operates direct-to-API from Node. No persistent storage of the token in any env file. To re-auth (e.g. after Zola signs you out), just sign back in to zola.com.
100
123
 
101
- Launches Chrome with a dedicated profile at `~/.zola-mcp/chrome-profile`, waits for you to sign in at `zola.com/account/login`, captures the `usr` cookie (a ~1-year JWT), and either prints it (for pasting into Claude Desktop / MCPB / any config that doesn't read `.env`) or writes it to the file you pass. Requires Google Chrome installed locally; the script will install `puppeteer-core` on first run (~1 MB).
124
+ You can opt out of this fallback with `ZOLA_DISABLE_FETCHPROXY=1` (e.g. in headless / CI environments where no extension is available).
102
125
 
103
126
  #### Option B — manual (DevTools)
104
127
 
@@ -117,11 +140,10 @@ Ask Claude: *"How's wedding planning going?"* — it should show your wedding da
117
140
 
118
141
  ## Credentials
119
142
 
120
- Only one credential is required:
121
-
122
143
  | Env var | Required | Notes |
123
144
  |---------|----------|-------|
124
- | `ZOLA_REFRESH_TOKEN` | Yes | Refresh token JWT (~1 year lifetime). Run `npm run auth` to capture via browser login, or copy the `usr` cookie from DevTools. |
145
+ | `ZOLA_REFRESH_TOKEN` | Conditional | Refresh token JWT (~1 year lifetime). When unset, the MCP falls back to the [fetchproxy extension](https://github.com/chrischall/fetchproxy) to read the `usr` cookie from your signed-in zola.com tab. |
146
+ | `ZOLA_DISABLE_FETCHPROXY` | No | Set to `1` to opt out of the fetchproxy fallback (headless / CI). |
125
147
  | `ZOLA_ACCOUNT_ID` | No | Auto-resolved from API on first use |
126
148
  | `ZOLA_REGISTRY_ID` | No | Auto-resolved from API on first use |
127
149
 
@@ -198,9 +220,9 @@ Only one credential is required:
198
220
 
199
221
  ## Troubleshooting
200
222
 
201
- **"ZOLA_REFRESH_TOKEN must be set"** — run `npm run auth` to capture your token.
223
+ **"Zola auth: set ZOLA_REFRESH_TOKEN, or install the fetchproxy extension…"** — either set `ZOLA_REFRESH_TOKEN` in your config or install the fetchproxy extension and sign into zola.com.
202
224
 
203
- **"Zola session refresh failed"** — your refresh token has expired (~1 year). Re-run `npm run auth`.
225
+ **"Zola session refresh failed"** — your refresh token has expired (~1 year) or been revoked. Either capture a new `usr` cookie (DevTools) or sign back into zola.com with the fetchproxy extension installed.
204
226
 
205
227
  **403 from mobile API** — the `x-zola-session-id` header may be missing. Update to the latest version.
206
228
 
@@ -208,7 +230,7 @@ Only one credential is required:
208
230
 
209
231
  ## Security
210
232
 
211
- - The refresh token lives only in your local `.env` or config file
233
+ - The refresh token lives only in your local `.env` or config file (when set) or in the user's browser cookie store (fetchproxy path)
212
234
  - It is passed as an environment variable and never logged
213
235
  - The server authenticates with Zola's mobile API using the same flow as the iOS app
214
236
  - Account and registry IDs are auto-resolved from the API (no manual configuration needed)
package/dist/auth.js ADDED
@@ -0,0 +1,125 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Auth resolution — Pattern A template
3
+ // ────────────────────────────────────────────────────────────────────────────
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.
9
+ //
10
+ // THE THREE PATHS, in priority order:
11
+ //
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.
16
+ //
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.
25
+ //
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.
47
+ import { bootstrap } from '@fetchproxy/bootstrap';
48
+ import pkg from '../package.json' with { type: 'json' };
49
+ /**
50
+ * Read an env var, trim, and treat blank / `${UNEXPANDED}` placeholders as
51
+ * unset. Defends against MCP hosts that pass `.mcp.json` env blocks through
52
+ * without variable expansion.
53
+ */
54
+ function readEnv(key) {
55
+ const raw = process.env[key];
56
+ if (typeof raw !== 'string')
57
+ return undefined;
58
+ const trimmed = raw.trim();
59
+ if (trimmed.length === 0)
60
+ return undefined;
61
+ if (trimmed === 'undefined' || trimmed === 'null')
62
+ return undefined;
63
+ if (/^\$\{[^}]*\}$/.test(trimmed))
64
+ return undefined;
65
+ return trimmed;
66
+ }
67
+ /** True if the user has explicitly disabled the fetchproxy fallback. */
68
+ function fetchproxyDisabled() {
69
+ const raw = readEnv('ZOLA_DISABLE_FETCHPROXY');
70
+ if (raw === undefined)
71
+ return false;
72
+ return ['1', 'true', 'yes', 'on'].includes(raw.toLowerCase());
73
+ }
74
+ /**
75
+ * Resolve the Zola refresh token using the three-path priority described
76
+ * above. Throws with an actionable error message when no path succeeds.
77
+ *
78
+ * Callers (i.e. `ZolaClient.refresh()`) should treat the return value as
79
+ * opaque credentials — do not branch on `source`. The field exists for
80
+ * logging / future cache-keying only.
81
+ */
82
+ export async function resolveRefreshToken() {
83
+ // ── Path 1: env-var refresh token (unchanged from pre-fetchproxy behavior).
84
+ const envToken = readEnv('ZOLA_REFRESH_TOKEN');
85
+ if (envToken) {
86
+ return { token: envToken, source: 'env' };
87
+ }
88
+ // ── Path 2: fetchproxy fallback (new).
89
+ if (!fetchproxyDisabled()) {
90
+ try {
91
+ const session = await bootstrap({
92
+ serverName: pkg.name,
93
+ version: pkg.version,
94
+ // Zola serves www.zola.com (web app) and mobile-api.zola.com (API).
95
+ // The `usr` cookie lives on the web app's apex domain; the extension
96
+ // matches on suffix so listing the apex covers any subdomain.
97
+ domains: ['zola.com'],
98
+ declare: {
99
+ // `usr` is HttpOnly → invisible to page JS, but fetchproxy 0.3.0's
100
+ // read_cookies uses `chrome.cookies.get` which DOES see HttpOnly
101
+ // cookies. The value IS the ~1-year refresh JWT.
102
+ cookies: ['usr'],
103
+ localStorage: [],
104
+ sessionStorage: [],
105
+ captureHeaders: [],
106
+ },
107
+ });
108
+ const token = session.cookies['usr'];
109
+ if (!token) {
110
+ throw new Error('zola: no `usr` cookie found. ' +
111
+ 'Sign into zola.com in your browser (with the fetchproxy extension installed) and retry.');
112
+ }
113
+ return { token, source: 'fetchproxy' };
114
+ }
115
+ catch (e) {
116
+ const msg = e instanceof Error ? e.message : String(e);
117
+ throw new Error(`Zola auth: no ZOLA_REFRESH_TOKEN set, and fetchproxy fallback failed: ${msg}`);
118
+ }
119
+ }
120
+ // ── Path 3: nothing configured. Surface both fixes side-by-side so the
121
+ // user can pick whichever fits their setup.
122
+ throw new Error('Zola auth: set ZOLA_REFRESH_TOKEN, ' +
123
+ 'or install the fetchproxy extension and sign into zola.com ' +
124
+ '(unset ZOLA_DISABLE_FETCHPROXY if it is set).');
125
+ }