zola-mcp 1.4.0 → 1.4.2
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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/auth.js +53 -102
- package/dist/bundle.js +151 -73
- package/dist/client.js +5 -2
- package/dist/index.js +1 -1
- package/dist/types.js +4 -7
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Zola wedding planning tools for Claude Code",
|
|
10
|
-
"version": "1.4.
|
|
10
|
+
"version": "1.4.2"
|
|
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.4.
|
|
18
|
+
"version": "1.4.2",
|
|
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.4.
|
|
4
|
+
"version": "1.4.2",
|
|
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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chris Hall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Zola MCP
|
|
2
2
|
|
|
3
|
+
[](https://github.com/chrischall/zola-mcp/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/zola-mcp)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
A [Model Context Protocol](https://modelcontextprotocol.io) server that connects Claude to [Zola](https://www.zola.com), giving you natural-language access to your wedding vendors, budget, guest list, seating chart, events, registry, inquiries, and more.
|
|
4
8
|
|
|
5
9
|
> [!WARNING]
|
|
@@ -301,3 +305,4 @@ Releases are automated via GitHub Actions:
|
|
|
301
305
|
## License
|
|
302
306
|
|
|
303
307
|
MIT
|
|
308
|
+
|
package/dist/auth.js
CHANGED
|
@@ -1,60 +1,63 @@
|
|
|
1
1
|
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// Auth resolution —
|
|
2
|
+
// Auth resolution — createAuthResolver (shared skeleton from mcp-utils)
|
|
3
3
|
// ────────────────────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
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
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
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 {
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
}
|
package/dist/bundle.js
CHANGED
|
@@ -34680,24 +34680,52 @@ async function runMcp(opts) {
|
|
|
34680
34680
|
return server;
|
|
34681
34681
|
}
|
|
34682
34682
|
|
|
34683
|
-
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34684
|
-
function textResult(data) {
|
|
34685
|
-
return {
|
|
34686
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34687
|
-
};
|
|
34688
|
-
}
|
|
34689
|
-
function imageResult(base643, mimeType) {
|
|
34690
|
-
return {
|
|
34691
|
-
content: [{ type: "image", data: base643, mimeType }]
|
|
34692
|
-
};
|
|
34693
|
-
}
|
|
34694
|
-
|
|
34695
34683
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
34696
34684
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
34685
|
+
var McpToolError = class extends Error {
|
|
34686
|
+
/** Actionable remediation text, when one applies. */
|
|
34687
|
+
hint;
|
|
34688
|
+
constructor(message, opts) {
|
|
34689
|
+
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
34690
|
+
this.name = "McpToolError";
|
|
34691
|
+
if (opts?.hint !== void 0)
|
|
34692
|
+
this.hint = opts.hint;
|
|
34693
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34694
|
+
}
|
|
34695
|
+
};
|
|
34696
|
+
var SessionNotAuthenticatedError = class extends McpToolError {
|
|
34697
|
+
constructor(service, signInHost) {
|
|
34698
|
+
const name = service ?? "the service";
|
|
34699
|
+
const where = signInHost ? `Open ${signInHost} in your browser and sign in, then try again.` : "Sign in in your browser, then try again.";
|
|
34700
|
+
super(`Not signed in to ${name}. ${where} Saved searches, saved homes, and other account data require a signed-in session.`, { hint: where });
|
|
34701
|
+
this.name = "SessionNotAuthenticatedError";
|
|
34702
|
+
}
|
|
34703
|
+
};
|
|
34704
|
+
function createHelpfulError(message, opts) {
|
|
34705
|
+
return new McpToolError(message, opts);
|
|
34706
|
+
}
|
|
34697
34707
|
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
34698
34708
|
var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
34709
|
+
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
34710
|
+
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,\s]+)=[^;,\s]*/gi;
|
|
34711
|
+
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,\s]+=[^;,\s]*)(?:;\s*[^=;,\s]+=[^;,\s]*)*)/gi;
|
|
34712
|
+
var API_KEY_RE = new RegExp([
|
|
34713
|
+
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
34714
|
+
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
34715
|
+
"gh[pousr]_[A-Za-z0-9]{36,}\\b",
|
|
34716
|
+
// GitHub ghp_/gho_/ghu_/ghs_/ghr_
|
|
34717
|
+
"xox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])",
|
|
34718
|
+
// Slack
|
|
34719
|
+
"AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])",
|
|
34720
|
+
// Google API key (39 chars total)
|
|
34721
|
+
"AKIA[0-9A-Z]{16}\\b",
|
|
34722
|
+
// AWS access key id (20 chars total)
|
|
34723
|
+
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
34724
|
+
// webhook signing secret (Stripe-style)
|
|
34725
|
+
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34726
|
+
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
34699
34727
|
function redactSecrets(text) {
|
|
34700
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
34728
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
34701
34729
|
}
|
|
34702
34730
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
34703
34731
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -34707,6 +34735,18 @@ function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
|
34707
34735
|
return `${redacted.slice(0, max)}\u2026 [truncated]`;
|
|
34708
34736
|
}
|
|
34709
34737
|
|
|
34738
|
+
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34739
|
+
function textResult(data) {
|
|
34740
|
+
return {
|
|
34741
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34742
|
+
};
|
|
34743
|
+
}
|
|
34744
|
+
function imageResult(base643, mimeType) {
|
|
34745
|
+
return {
|
|
34746
|
+
content: [{ type: "image", data: base643, mimeType }]
|
|
34747
|
+
};
|
|
34748
|
+
}
|
|
34749
|
+
|
|
34710
34750
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34711
34751
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
34712
34752
|
function readEnvVar(key, opts = {}) {
|
|
@@ -34720,6 +34760,20 @@ function readEnvVar(key, opts = {}) {
|
|
|
34720
34760
|
}
|
|
34721
34761
|
return opts.default;
|
|
34722
34762
|
}
|
|
34763
|
+
var TRUE_TOKENS = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
34764
|
+
var FALSE_TOKENS = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
|
|
34765
|
+
function parseBoolEnv(key, opts = {}) {
|
|
34766
|
+
const fallback = opts.default ?? false;
|
|
34767
|
+
const raw = readEnvVar(key, { env: opts.env });
|
|
34768
|
+
if (raw === void 0)
|
|
34769
|
+
return fallback;
|
|
34770
|
+
const token = raw.toLowerCase();
|
|
34771
|
+
if (TRUE_TOKENS.has(token))
|
|
34772
|
+
return true;
|
|
34773
|
+
if (FALSE_TOKENS.has(token))
|
|
34774
|
+
return false;
|
|
34775
|
+
return fallback;
|
|
34776
|
+
}
|
|
34723
34777
|
async function loadDotenvSafely(opts = {}) {
|
|
34724
34778
|
try {
|
|
34725
34779
|
const mod = await import(
|
|
@@ -34738,6 +34792,15 @@ async function loadDotenvSafely(opts = {}) {
|
|
|
34738
34792
|
}
|
|
34739
34793
|
|
|
34740
34794
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
34795
|
+
function buildOptionalBody(args, optionalFields) {
|
|
34796
|
+
const body = {};
|
|
34797
|
+
for (const field of optionalFields) {
|
|
34798
|
+
if (args[field] !== void 0) {
|
|
34799
|
+
body[field] = args[field];
|
|
34800
|
+
}
|
|
34801
|
+
}
|
|
34802
|
+
return body;
|
|
34803
|
+
}
|
|
34741
34804
|
function formatApiError(status, method, path, errorText, opts = {}) {
|
|
34742
34805
|
const service = opts.service ?? "API";
|
|
34743
34806
|
const head = `${service} error ${status} for ${method.toUpperCase()} ${path}`;
|
|
@@ -34796,6 +34859,47 @@ var pageSchema = {
|
|
|
34796
34859
|
page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
|
|
34797
34860
|
};
|
|
34798
34861
|
|
|
34862
|
+
// node_modules/@chrischall/mcp-utils/dist/auth/index.js
|
|
34863
|
+
function bridgeDownHintOf(err) {
|
|
34864
|
+
if (err instanceof Error && err.name === "FetchproxyBridgeDownError") {
|
|
34865
|
+
const hint = err.hint;
|
|
34866
|
+
if (typeof hint === "string" && hint.length > 0)
|
|
34867
|
+
return hint;
|
|
34868
|
+
}
|
|
34869
|
+
return void 0;
|
|
34870
|
+
}
|
|
34871
|
+
function createAuthResolver(opts) {
|
|
34872
|
+
const { envVar, disableEnvVar, bootstrap: bootstrap2, bootstrapOptions, parseTokens, serviceName, signInHost, env } = opts;
|
|
34873
|
+
return async function resolveAuth2() {
|
|
34874
|
+
const envCredential = readEnvVar(envVar, env ? { env } : {});
|
|
34875
|
+
if (envCredential) {
|
|
34876
|
+
return { credential: envCredential, source: "env" };
|
|
34877
|
+
}
|
|
34878
|
+
const disabled = disableEnvVar !== void 0 && parseBoolEnv(disableEnvVar, env ? { env } : {});
|
|
34879
|
+
if (!disabled) {
|
|
34880
|
+
let session;
|
|
34881
|
+
try {
|
|
34882
|
+
session = await bootstrap2(bootstrapOptions);
|
|
34883
|
+
} catch (e) {
|
|
34884
|
+
const bridgeHint = bridgeDownHintOf(e);
|
|
34885
|
+
if (bridgeHint !== void 0) {
|
|
34886
|
+
throw createHelpfulError(`Auth: no ${envVar} set, and the fetchproxy bridge is down (extension service worker unreachable). ${bridgeHint}`, { hint: bridgeHint });
|
|
34887
|
+
}
|
|
34888
|
+
throw createHelpfulError(`Auth: no ${envVar} set, and fetchproxy fallback failed: ${truncateErrorMessage(messageOf(e))}`, { hint: `Set ${envVar}, or sign in in your browser and retry.` });
|
|
34889
|
+
}
|
|
34890
|
+
const credential = parseTokens(session);
|
|
34891
|
+
if (credential) {
|
|
34892
|
+
return { credential, source: "fetchproxy" };
|
|
34893
|
+
}
|
|
34894
|
+
throw new SessionNotAuthenticatedError(serviceName, signInHost);
|
|
34895
|
+
}
|
|
34896
|
+
throw createHelpfulError(`Auth: set ${envVar}${signInHost ? `, or sign in at ${signInHost} in your browser` : ", or sign in in your browser"}${disableEnvVar ? ` (unset ${disableEnvVar} if it is set)` : ""}.`, { hint: `Set ${envVar} or sign in in your browser.` });
|
|
34897
|
+
};
|
|
34898
|
+
}
|
|
34899
|
+
function messageOf(err) {
|
|
34900
|
+
return err instanceof Error ? err.message : String(err);
|
|
34901
|
+
}
|
|
34902
|
+
|
|
34799
34903
|
// src/client.ts
|
|
34800
34904
|
import { dirname, join as join2 } from "path";
|
|
34801
34905
|
import { fileURLToPath } from "url";
|
|
@@ -37980,7 +38084,7 @@ var BootstrapDisabledError = class extends Error {
|
|
|
37980
38084
|
// package.json
|
|
37981
38085
|
var package_default = {
|
|
37982
38086
|
name: "zola-mcp",
|
|
37983
|
-
version: "1.4.
|
|
38087
|
+
version: "1.4.2",
|
|
37984
38088
|
mcpName: "io.github.chrischall/zola-mcp",
|
|
37985
38089
|
description: "Zola wedding MCP server for Claude",
|
|
37986
38090
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38026,7 +38130,7 @@ var package_default = {
|
|
|
38026
38130
|
"test:watch": "vitest"
|
|
38027
38131
|
},
|
|
38028
38132
|
dependencies: {
|
|
38029
|
-
"@chrischall/mcp-utils": "^0.
|
|
38133
|
+
"@chrischall/mcp-utils": "^0.7.0",
|
|
38030
38134
|
"@fetchproxy/bootstrap": "^1.3.0",
|
|
38031
38135
|
"@fetchproxy/server": "^1.3.0",
|
|
38032
38136
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38043,58 +38147,36 @@ var package_default = {
|
|
|
38043
38147
|
};
|
|
38044
38148
|
|
|
38045
38149
|
// src/auth.ts
|
|
38046
|
-
|
|
38047
|
-
|
|
38048
|
-
|
|
38049
|
-
|
|
38050
|
-
|
|
38051
|
-
|
|
38052
|
-
|
|
38053
|
-
|
|
38054
|
-
|
|
38055
|
-
|
|
38056
|
-
|
|
38057
|
-
|
|
38058
|
-
|
|
38059
|
-
|
|
38060
|
-
|
|
38061
|
-
|
|
38062
|
-
|
|
38063
|
-
|
|
38064
|
-
|
|
38065
|
-
|
|
38066
|
-
|
|
38067
|
-
// read_cookies uses `chrome.cookies.get` which DOES see HttpOnly
|
|
38068
|
-
// cookies. The value IS the ~1-year refresh JWT.
|
|
38069
|
-
cookies: ["usr"],
|
|
38070
|
-
localStorage: [],
|
|
38071
|
-
sessionStorage: [],
|
|
38072
|
-
captureHeaders: []
|
|
38073
|
-
}
|
|
38074
|
-
});
|
|
38075
|
-
const token = session.cookies["usr"];
|
|
38076
|
-
if (!token) {
|
|
38077
|
-
throw new Error(
|
|
38078
|
-
"zola: no `usr` cookie found. Sign into zola.com in your browser (with the fetchproxy extension installed) and retry."
|
|
38079
|
-
);
|
|
38080
|
-
}
|
|
38081
|
-
return { token, source: "fetchproxy" };
|
|
38082
|
-
} catch (e) {
|
|
38083
|
-
if (classifyBridgeError(e) === "bridge_down") {
|
|
38084
|
-
const downErr = e;
|
|
38085
|
-
throw new Error(
|
|
38086
|
-
`Zola auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
|
|
38087
|
-
);
|
|
38088
|
-
}
|
|
38089
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
38090
|
-
throw new Error(
|
|
38091
|
-
`Zola auth: no ZOLA_REFRESH_TOKEN set, and fetchproxy fallback failed: ${msg}`
|
|
38092
|
-
);
|
|
38150
|
+
var resolveAuth = createAuthResolver({
|
|
38151
|
+
envVar: "ZOLA_REFRESH_TOKEN",
|
|
38152
|
+
disableEnvVar: "ZOLA_DISABLE_FETCHPROXY",
|
|
38153
|
+
// The real bootstrap has a narrower opts type than the injected boundary;
|
|
38154
|
+
// the cast keeps the heavy bridge dep out of the shared module's types.
|
|
38155
|
+
bootstrap,
|
|
38156
|
+
bootstrapOptions: {
|
|
38157
|
+
serverName: package_default.name,
|
|
38158
|
+
version: package_default.version,
|
|
38159
|
+
// Zola serves www.zola.com (web app) and mobile-api.zola.com (API).
|
|
38160
|
+
// The `usr` cookie lives on the web app's apex domain; the extension
|
|
38161
|
+
// matches on suffix so listing the apex covers any subdomain.
|
|
38162
|
+
domains: ["zola.com"],
|
|
38163
|
+
declare: {
|
|
38164
|
+
// `usr` is HttpOnly → invisible to page JS, but fetchproxy's
|
|
38165
|
+
// read_cookies uses `chrome.cookies.get` which DOES see HttpOnly
|
|
38166
|
+
// cookies. The value IS the ~1-year refresh JWT.
|
|
38167
|
+
cookies: ["usr"],
|
|
38168
|
+
localStorage: [],
|
|
38169
|
+
sessionStorage: [],
|
|
38170
|
+
captureHeaders: []
|
|
38093
38171
|
}
|
|
38094
|
-
}
|
|
38095
|
-
|
|
38096
|
-
|
|
38097
|
-
|
|
38172
|
+
},
|
|
38173
|
+
parseTokens: (session) => session.cookies["usr"],
|
|
38174
|
+
serviceName: "Zola",
|
|
38175
|
+
signInHost: "zola.com"
|
|
38176
|
+
});
|
|
38177
|
+
async function resolveRefreshToken() {
|
|
38178
|
+
const { credential, source } = await resolveAuth();
|
|
38179
|
+
return { token: credential, source };
|
|
38098
38180
|
}
|
|
38099
38181
|
|
|
38100
38182
|
// src/client.ts
|
|
@@ -38247,7 +38329,7 @@ var ZolaClient = class {
|
|
|
38247
38329
|
if (!response.ok) {
|
|
38248
38330
|
const text = await response.text();
|
|
38249
38331
|
throw new Error(
|
|
38250
|
-
`Zola session refresh failed (${response.status}): ${text}
|
|
38332
|
+
`Zola session refresh failed (${response.status}): ${truncateErrorMessage(text)}
|
|
38251
38333
|
To fix: set ZOLA_REFRESH_TOKEN, or install the fetchproxy extension and sign into zola.com.`
|
|
38252
38334
|
);
|
|
38253
38335
|
}
|
|
@@ -38266,11 +38348,7 @@ function imageResult2(bytes, mimeType) {
|
|
|
38266
38348
|
return imageResult(Buffer.from(bytes).toString("base64"), mimeType);
|
|
38267
38349
|
}
|
|
38268
38350
|
function pickDefined(base, args, keys) {
|
|
38269
|
-
|
|
38270
|
-
for (const key of keys) {
|
|
38271
|
-
if (args[key] !== void 0) body[key] = args[key];
|
|
38272
|
-
}
|
|
38273
|
-
return body;
|
|
38351
|
+
return { ...base, ...buildOptionalBody(args, keys) };
|
|
38274
38352
|
}
|
|
38275
38353
|
|
|
38276
38354
|
// src/tools/vendors.ts
|
|
@@ -40225,7 +40303,7 @@ function registerEventInvitationTools(server) {
|
|
|
40225
40303
|
}
|
|
40226
40304
|
|
|
40227
40305
|
// src/index.ts
|
|
40228
|
-
var VERSION = "1.4.
|
|
40306
|
+
var VERSION = "1.4.2";
|
|
40229
40307
|
await runMcp({
|
|
40230
40308
|
name: "zola-mcp",
|
|
40231
40309
|
version: VERSION,
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import { readEnvVar, loadDotenvSafely, decodeJwtExp, decodeJwtSessionId, formatApiError, } from '@chrischall/mcp-utils';
|
|
3
|
+
import { readEnvVar, loadDotenvSafely, decodeJwtExp, decodeJwtSessionId, formatApiError, truncateErrorMessage, } from '@chrischall/mcp-utils';
|
|
4
4
|
import { resolveRefreshToken } from './auth.js';
|
|
5
5
|
// Load `.env` next to the compiled entry point. `loadDotenvSafely` is a
|
|
6
6
|
// no-throw loader: in bundled mode (no resolvable `dotenv`) it returns false
|
|
@@ -159,7 +159,10 @@ export class ZolaClient {
|
|
|
159
159
|
});
|
|
160
160
|
if (!response.ok) {
|
|
161
161
|
const text = await response.text();
|
|
162
|
-
|
|
162
|
+
// The refresh request body carries the refresh JWT — an echoing upstream
|
|
163
|
+
// or proxy could reflect it in the error body, so redact + truncate the
|
|
164
|
+
// untrusted body before it can reach a tool result.
|
|
165
|
+
throw new Error(`Zola session refresh failed (${response.status}): ${truncateErrorMessage(text)}\n` +
|
|
163
166
|
'To fix: set ZOLA_REFRESH_TOKEN, or install the fetchproxy extension and sign into zola.com.');
|
|
164
167
|
}
|
|
165
168
|
const result = (await response.json());
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { registerWebsiteThemeTools } from './tools/website-theme.js';
|
|
|
12
12
|
import { registerRegistryItemTools } from './tools/registry-items.js';
|
|
13
13
|
import { registerInvitationTools } from './tools/invitations.js';
|
|
14
14
|
import { registerEventInvitationTools } from './tools/event-invitations.js';
|
|
15
|
-
const VERSION = '1.4.
|
|
15
|
+
const VERSION = '1.4.2'; // x-release-please-version
|
|
16
16
|
await runMcp({
|
|
17
17
|
name: 'zola-mcp',
|
|
18
18
|
version: VERSION,
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { textResult, imageResult as imageResultBase64 } from '@chrischall/mcp-utils';
|
|
1
|
+
import { buildOptionalBody, textResult, imageResult as imageResultBase64 } from '@chrischall/mcp-utils';
|
|
2
2
|
/**
|
|
3
3
|
* Wrap any value as the standard MCP text-content tool result with pretty-printed JSON.
|
|
4
4
|
* Re-exported from `@chrischall/mcp-utils` (`textResult`) under the repo's
|
|
@@ -16,12 +16,9 @@ export function imageResult(bytes, mimeType) {
|
|
|
16
16
|
/**
|
|
17
17
|
* Build a partial-update body containing only keys from `args` that are not undefined.
|
|
18
18
|
* Used by tools that send PATCH-style updates where omitting a key means "leave unchanged".
|
|
19
|
+
* The undefined-dropping is the shared `buildOptionalBody` from `@chrischall/mcp-utils`;
|
|
20
|
+
* this wrapper just merges the always-present `base` fields in front.
|
|
19
21
|
*/
|
|
20
22
|
export function pickDefined(base, args, keys) {
|
|
21
|
-
|
|
22
|
-
for (const key of keys) {
|
|
23
|
-
if (args[key] !== undefined)
|
|
24
|
-
body[key] = args[key];
|
|
25
|
-
}
|
|
26
|
-
return body;
|
|
23
|
+
return { ...base, ...buildOptionalBody(args, keys) };
|
|
27
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zola-mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"mcpName": "io.github.chrischall/zola-mcp",
|
|
5
5
|
"description": "Zola wedding MCP server for Claude",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test:watch": "vitest"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@chrischall/mcp-utils": "^0.
|
|
49
|
+
"@chrischall/mcp-utils": "^0.7.0",
|
|
50
50
|
"@fetchproxy/bootstrap": "^1.3.0",
|
|
51
51
|
"@fetchproxy/server": "^1.3.0",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/zola-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.4.
|
|
9
|
+
"version": "1.4.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "zola-mcp",
|
|
14
|
-
"version": "1.4.
|
|
14
|
+
"version": "1.4.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|