zola-mcp 1.1.2 → 1.1.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +14 -14
- package/dist/auth.js +125 -0
- package/dist/bundle.js +8878 -3051
- package/dist/client.js +7 -4
- package/dist/index.js +1 -1
- package/package.json +3 -4
- package/server.json +4 -4
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Zola wedding planning tools for Claude Code",
|
|
10
|
-
"version": "1.1.
|
|
10
|
+
"version": "1.1.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.
|
|
18
|
+
"version": "1.1.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.
|
|
4
|
+
"version": "1.1.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,7 @@ 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
|
-
- [
|
|
26
|
+
- For the no-env-var path: the [fetchproxy 0.3.0 Chrome / Safari extension](https://github.com/chrischall/fetchproxy)
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
@@ -89,16 +89,17 @@ Add to Claude Desktop config:
|
|
|
89
89
|
|
|
90
90
|
### Getting your refresh token
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
You have two options. Both produce the same `usr` cookie value — a ~1-year JWT that doubles as the refresh token.
|
|
93
93
|
|
|
94
|
-
#### Option A —
|
|
94
|
+
#### Option A — fetchproxy extension (recommended)
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
```
|
|
96
|
+
1. Install the [fetchproxy 0.3.0 extension](https://github.com/chrischall/fetchproxy) (Chrome Web Store or Safari `.dmg`).
|
|
97
|
+
2. Sign in at [zola.com/account/login](https://www.zola.com/account/login) in that browser.
|
|
98
|
+
3. Leave `ZOLA_REFRESH_TOKEN` **unset** in your Claude config.
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
You can opt out of this fallback with `ZOLA_DISABLE_FETCHPROXY=1` (e.g. in headless / CI environments where no extension is available).
|
|
102
103
|
|
|
103
104
|
#### Option B — manual (DevTools)
|
|
104
105
|
|
|
@@ -117,11 +118,10 @@ Ask Claude: *"How's wedding planning going?"* — it should show your wedding da
|
|
|
117
118
|
|
|
118
119
|
## Credentials
|
|
119
120
|
|
|
120
|
-
Only one credential is required:
|
|
121
|
-
|
|
122
121
|
| Env var | Required | Notes |
|
|
123
122
|
|---------|----------|-------|
|
|
124
|
-
| `ZOLA_REFRESH_TOKEN` |
|
|
123
|
+
| `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. |
|
|
124
|
+
| `ZOLA_DISABLE_FETCHPROXY` | No | Set to `1` to opt out of the fetchproxy fallback (headless / CI). |
|
|
125
125
|
| `ZOLA_ACCOUNT_ID` | No | Auto-resolved from API on first use |
|
|
126
126
|
| `ZOLA_REGISTRY_ID` | No | Auto-resolved from API on first use |
|
|
127
127
|
|
|
@@ -198,9 +198,9 @@ Only one credential is required:
|
|
|
198
198
|
|
|
199
199
|
## Troubleshooting
|
|
200
200
|
|
|
201
|
-
**"ZOLA_REFRESH_TOKEN
|
|
201
|
+
**"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
202
|
|
|
203
|
-
**"Zola session refresh failed"** — your refresh token has expired (~1 year).
|
|
203
|
+
**"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
204
|
|
|
205
205
|
**403 from mobile API** — the `x-zola-session-id` header may be missing. Update to the latest version.
|
|
206
206
|
|
|
@@ -208,7 +208,7 @@ Only one credential is required:
|
|
|
208
208
|
|
|
209
209
|
## Security
|
|
210
210
|
|
|
211
|
-
- The refresh token lives only in your local `.env` or config file
|
|
211
|
+
- 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
212
|
- It is passed as an environment variable and never logged
|
|
213
213
|
- The server authenticates with Zola's mobile API using the same flow as the iOS app
|
|
214
214
|
- 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
|
+
}
|