@wix/mcp 1.0.86 → 1.0.88
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/build/bin-standalone.js +61 -8
- package/build/cjs/index.cjs +93 -12
- package/build/cjs/toolkit.cjs +91 -10
- package/build/dts/code-mode/index.js +40 -2
- package/build/dts/config/api-request-rules.d.ts +1 -0
- package/build/dts/config/api-request-rules.js +1 -0
- package/build/dts/config/default-config.js +42 -3
- package/build/dts/config/param-descriptions.js +5 -2
- package/build/dts/docs/get-to-know.js +14 -11
- package/build/dts/import-claude-design-tool/index.js +2 -2
- package/build/dts/tool-utils.js +12 -1
- package/build/esm/index.js +93 -12
- package/build/esm/toolkit.js +91 -10
- package/package.json +2 -2
package/build/cjs/toolkit.cjs
CHANGED
|
@@ -17655,6 +17655,7 @@ function rethrowIfUnauthorized(error) {
|
|
|
17655
17655
|
|
|
17656
17656
|
// src/config/api-request-rules.ts
|
|
17657
17657
|
var ARRAY_QUERY_PARAMS_RULE = "Repeat array query params once per value: `?fieldsets=URL&fieldsets=RICH_CONTENT`. Do NOT comma-join values (`fieldsets=URL,RICH_CONTENT`), do NOT leave a trailing comma, and do NOT use a value the method schema does not list, even if a sibling method accepts it (draft posts have no `SEO` fieldset).";
|
|
17658
|
+
var WQL_FILTER_RULE = "Query and search filters are written in WQL: each field takes a single operator, so combine conditions with the logical operators \u2014 `$and` and `$or` take an array of expressions, `$not` takes one, and they can nest.";
|
|
17658
17659
|
|
|
17659
17660
|
// src/config/default-config.ts
|
|
17660
17661
|
var AGENT_GOAL = dedent_default`
|
|
@@ -17755,7 +17756,7 @@ var SITE_CREATION_COPY = {
|
|
|
17755
17756
|
var SITE_GUIDANCE = dedent_default`
|
|
17756
17757
|
A new-site request has two things to settle: what the site is for, and how to build it. The how is AI generation vs. starting from a template, so naming templates, a template id or Wix Studio (Wix Studio is also the template side) already settles it.
|
|
17757
17758
|
|
|
17758
|
-
One case skips all of this: the user ALREADY HAS the finished site as files — hand-written HTML/CSS/JS, a zip, or the export of an AI builder. There is nothing to build, so don't offer AI or templates and don't call any site-creation tool.
|
|
17759
|
+
One case skips all of this: the user ALREADY HAS the finished site as files — hand-written HTML/CSS/JS, a zip, or the export of an AI builder. There is nothing to build, so don't offer AI or templates and don't call any site-creation tool. For a full agentic flow, read the recipe first and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it walks you through creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply (you can't reach the file bytes, or something fails), fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp — they drag the files in and get a live Wix-hosted site. Give that URL exactly as written, keeping the utm_campaign=mcp parameter.
|
|
17759
17760
|
|
|
17760
17761
|
Settle what the site is for first: its purpose, topic, business type, audience, or main visitor action. Naming a method is not a description of the site, and neither is the business or industry on its own.
|
|
17761
17762
|
If you can't tell yet, ask the preferred question below and nothing else — one question, no creation-method question in the same reply, and no internal tool names.
|
|
@@ -17814,18 +17815,56 @@ var ACCOUNT_SITE_EXECUTE_SCOPE = dedent_default`
|
|
|
17814
17815
|
siteId?: string;
|
|
17815
17816
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
17816
17817
|
url: string;
|
|
17818
|
+
// Plain object → JSON. A string is sent verbatim (set your own Content-Type,
|
|
17819
|
+
// e.g. multipart/form-data). A Uint8Array/ArrayBuffer is sent as raw bytes
|
|
17820
|
+
// for binary uploads (set your own Content-Type).
|
|
17817
17821
|
body?: unknown;
|
|
17818
17822
|
headers?: Record<string, string>;
|
|
17823
|
+
// "base64" returns the response body as a base64 string (data), with
|
|
17824
|
+
// encoding: "base64" on the result — use it for binary downloads (zip,
|
|
17825
|
+
// image, pdf); decoding those as text corrupts them. Binary content types
|
|
17826
|
+
// are returned as base64 automatically even without this. Decode, edit and
|
|
17827
|
+
// re-upload binary WITHIN this script and return only a small summary. If you
|
|
17828
|
+
// must return the bytes, return the wix.request response object itself (so
|
|
17829
|
+
// encoding: "base64" is preserved and it isn't truncated); a bare base64
|
|
17830
|
+
// string you return is treated as plain text and may be truncated.
|
|
17831
|
+
responseType?: "base64";
|
|
17819
17832
|
}
|
|
17820
17833
|
interface WixResponse<T = unknown> {
|
|
17821
17834
|
status: number;
|
|
17822
17835
|
data: T;
|
|
17836
|
+
encoding?: "base64";
|
|
17823
17837
|
json(): Promise<T>;
|
|
17824
17838
|
}
|
|
17825
17839
|
declare const wix: {
|
|
17826
17840
|
request<T = unknown>(options: WixRequestOptions): Promise<WixResponse<T>>;
|
|
17841
|
+
// Build a multipart/form-data body from files (defaults to the \`files\`
|
|
17842
|
+
// global). Each file becomes a part named fieldName (default "files") with
|
|
17843
|
+
// its path as the filename. Pass body + contentType to wix.request.
|
|
17844
|
+
multipart(
|
|
17845
|
+
files?: Array<{ path: string; content: string; contentType?: string }>,
|
|
17846
|
+
options?: { fieldName?: string }
|
|
17847
|
+
): { body: string; contentType: string };
|
|
17827
17848
|
};
|
|
17828
17849
|
declare const siteId: string | undefined;
|
|
17850
|
+
// Files from the ExecuteWixAPI \`files\` param (raw content, no escaping).
|
|
17851
|
+
declare const files: Array<{ path: string; content: string }>;
|
|
17852
|
+
\`\`\`
|
|
17853
|
+
|
|
17854
|
+
FILE UPLOADS: pass file bodies via the \`files\` param (not inside \`code\` — no escaping), then build the multipart body with \`wix.multipart()\`:
|
|
17855
|
+
\`\`\`javascript
|
|
17856
|
+
// files param: "=== FILE: index.html ===\\n<!doctype html>...\\n=== FILE: app.js ===\\nconsole.log(1)"
|
|
17857
|
+
async () => {
|
|
17858
|
+
const mp = wix.multipart(); // parts named "files", filename = each path
|
|
17859
|
+
const res = await wix.request({
|
|
17860
|
+
scope: "account",
|
|
17861
|
+
method: "POST",
|
|
17862
|
+
url: "https://www.wixapis.com/.../upload",
|
|
17863
|
+
headers: { "Content-Type": mp.contentType },
|
|
17864
|
+
body: mp.body
|
|
17865
|
+
});
|
|
17866
|
+
return res.data;
|
|
17867
|
+
}
|
|
17829
17868
|
\`\`\`
|
|
17830
17869
|
`;
|
|
17831
17870
|
var EXECUTE_WIX_API_WQL = dedent_default`
|
|
@@ -17958,6 +17997,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
17958
17997
|
ReadFullDocsArticle: dedent_default`
|
|
17959
17998
|
Fetches the full Wix docs article or method article with code examples for using the method.
|
|
17960
17999
|
Docs articles looks like this: https://dev.wix.com/docs/... and they can either be general docs articles or method articles.
|
|
18000
|
+
Take the URL from the user's message or an earlier tool result. To reach an article you have no URL for, search or browse the docs first.
|
|
17961
18001
|
For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include ?apiView=SDK.
|
|
17962
18002
|
${reminder}
|
|
17963
18003
|
`,
|
|
@@ -18090,7 +18130,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
18090
18130
|
`,
|
|
18091
18131
|
"import-claude-design-from-url": dedent_default`
|
|
18092
18132
|
Import a design created in Claude's design tool into Wix. Takes the signed claudeusercontent.com URL Claude Design produces for its self-contained HTML bundle (all images, fonts, and styles inlined); any other host is rejected. Creates a live Wix-hosted site and returns its URL.
|
|
18093
|
-
NOT for site files the user provides themselves (their own HTML/CSS/JS, a zip, another builder's export) — for those, don't call this tool:
|
|
18133
|
+
NOT for site files the user provides themselves (their own HTML/CSS/JS, a zip, another builder's export) — for those, don't call this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp, where they upload the files and get a live hosted site. Give that URL exactly as written, keeping the utm_campaign=mcp parameter.
|
|
18094
18134
|
`,
|
|
18095
18135
|
RunWixCliCommand: dedent_default`
|
|
18096
18136
|
Executes Wix CLI commands for Wix app development and management.
|
|
@@ -18517,7 +18557,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
|
|
|
18517
18557
|
CreateWixBusinessGuide: dedent_default`
|
|
18518
18558
|
Provides comprehensive documentation for creating a new Wix business (site, app, etc.).
|
|
18519
18559
|
Call this ONLY when the user explicitly asks for a headless site, a Wix Classic Editor site, or manual/advanced account/API-based site creation — these don't need the site's purpose first. For normal site-creation ${SITE_CREATION_COPY.normalCreationRoute.nonWidget}; for a specific template id, call \`CreateSiteFromTemplate\`.
|
|
18520
|
-
Exception, even when the user says "headless": if they ALREADY HAVE the finished site as files (their own HTML/CSS/JS, a zip, an AI-builder export) and want those files hosted, don't call this tool —
|
|
18560
|
+
Exception, even when the user says "headless": if they ALREADY HAVE the finished site as files (their own HTML/CSS/JS, a zip, an AI-builder export) and want those files hosted, don't call this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload the files and get a live hosted site (give that URL exactly, keeping the utm_campaign=mcp parameter). This guide's CLI flow is for BUILDING a headless frontend, not for hosting ready-made files.
|
|
18521
18561
|
|
|
18522
18562
|
## What This Tool Contains
|
|
18523
18563
|
|
|
@@ -19154,7 +19194,10 @@ var paramDescriptions = {
|
|
|
19154
19194
|
ARRAY_QUERY_PARAMS_RULE
|
|
19155
19195
|
].join("\n"),
|
|
19156
19196
|
method: "The HTTP method to use for the API call (e.g. GET, POST, PUT, DELETE)",
|
|
19157
|
-
body:
|
|
19197
|
+
body: [
|
|
19198
|
+
'The request body as a JSON object or array with all the required fields and values, including nested objects. Pass the actual value (object or array), NOT a JSON string. YOU MUST NEVER MAKE UP A BODY - the body should be based on the conversation context, i.e from the user prompt OR got into the conversation context by the "ReadFullDocsArticle" tool OR by the "SearchWixAPISpec" tool OR by the "ReadFullDocsMethodSchema" tool - i.e based on the API docs, a relevant recipe you read (preferably), a code example you found in the docs, a schema you read etc.. YOU MUST NEVER ASSUME YOU KNOW WHAT THE BODY SCHEMA IS WITHOUT CONCRETE EXAMPLES OR SCHEMA DEFINITIONS FROM THE CONVERSATION CONTEXT. Prefer reading relevant recipes if you have them in context for understand the body schema for API calls.',
|
|
19199
|
+
WQL_FILTER_RULE
|
|
19200
|
+
].join("\n"),
|
|
19158
19201
|
reason: "One sentence explaining the original user request and why you are calling this API to complete it.",
|
|
19159
19202
|
sourceDocUrl: [
|
|
19160
19203
|
"The URL of the documentation or recipe where you found this API endpoint.",
|
|
@@ -19478,15 +19521,17 @@ Failed to fetch content: ${errorMsg}`
|
|
|
19478
19521
|
const baseContentsPromise = Promise.all(
|
|
19479
19522
|
baseDocsWithoutRecipes.map(fetchDoc)
|
|
19480
19523
|
);
|
|
19481
|
-
const skillsContentPromise = getSkillsByPortal(httpClient, {
|
|
19524
|
+
const skillsContentPromise = skillsTags?.length ? getSkillsByPortal(httpClient, {
|
|
19482
19525
|
portalId: REST_PORTAL_ID,
|
|
19483
19526
|
...repo && { repo },
|
|
19484
19527
|
...commitHash && { commitHash },
|
|
19485
|
-
|
|
19528
|
+
tags: skillsTags
|
|
19486
19529
|
}).catch((err) => {
|
|
19487
19530
|
console.error("[WixREADME] Failed to fetch skills by portal:", err);
|
|
19488
19531
|
return null;
|
|
19489
|
-
})
|
|
19532
|
+
}) : fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
|
|
19533
|
+
(doc) => doc?.text ?? null
|
|
19534
|
+
);
|
|
19490
19535
|
const filter = {};
|
|
19491
19536
|
if (siteId) filter.siteId = siteId;
|
|
19492
19537
|
else if (siteName) filter.siteName = siteName;
|
|
@@ -20525,6 +20570,14 @@ var import_http_client2 = __toESM(require_index_node(), 1);
|
|
|
20525
20570
|
|
|
20526
20571
|
// src/tool-utils.ts
|
|
20527
20572
|
var REST_METHOD_CONTRACT_RETRY_HINT = "Before retrying, make sure you have everything needed for a correct call: the right endpoint URL, HTTP method, request body shape, and response shape. Use the SearchWixRESTDocumentation tool or BrowseWixRESTDocsMenu tool to find the correct method or relevant related articles, the ReadFullDocsArticle tool to read the method description and all code examples, and the SearchWixAPISpec tool for precise field names and schema details.";
|
|
20573
|
+
var SINGLE_OPERATOR_PER_FIELD_HINT = 'WQL takes one operator per field object. Split the conditions into separate expressions under the matching logical operator \u2014 `{"$and": [{"field": {"$gte": ...}}, {"field": {"$lte": ...}}]}` \u2014 and retry without re-searching the docs.';
|
|
20574
|
+
var FIELD_CAPABILITIES_HINT = "This endpoint declares its filterable and sortable fields with their operators. Read the map with SearchWixAPISpec (`queryMethodData.queryFieldsCapabilitiesMap`, or `searchMethodData.searchFieldsCapabilitiesMap` on search methods) and use only what it lists; otherwise fetch a bounded page and filter or sort the results yourself.";
|
|
20575
|
+
var getRetryHint = (errorDetails) => {
|
|
20576
|
+
if (!/Filter contains unsupported operator/i.test(errorDetails)) {
|
|
20577
|
+
return REST_METHOD_CONTRACT_RETRY_HINT;
|
|
20578
|
+
}
|
|
20579
|
+
return /"operator":"\{/.test(errorDetails) ? SINGLE_OPERATOR_PER_FIELD_HINT : FIELD_CAPABILITIES_HINT;
|
|
20580
|
+
};
|
|
20528
20581
|
var safeParseJSON = (text) => {
|
|
20529
20582
|
try {
|
|
20530
20583
|
return JSON.parse(text);
|
|
@@ -20548,7 +20601,7 @@ var buildErrorMessage = ({
|
|
|
20548
20601
|
].filter((str) => !!str).join("\n");
|
|
20549
20602
|
return `${baseMessage}
|
|
20550
20603
|
|
|
20551
|
-
${
|
|
20604
|
+
${getRetryHint(String(errorDetails))}`;
|
|
20552
20605
|
};
|
|
20553
20606
|
var handleWixAPIResponse = async (response) => {
|
|
20554
20607
|
const requestId = response.headers?.["x-wix-request-id"];
|
|
@@ -22215,12 +22268,31 @@ function formatExecuteResult(data, maxResponseSize, reportBi) {
|
|
|
22215
22268
|
reportBi?.({
|
|
22216
22269
|
originalLength: typeof data.result === "string" ? data.result.length : JSON.stringify(data.result)?.length
|
|
22217
22270
|
});
|
|
22271
|
+
const base64Text = base64PayloadText(data.result);
|
|
22272
|
+
if (base64Text !== null) {
|
|
22273
|
+
if (base64Text.length <= MAX_ALLOWED_RESPONSE_CHARS) {
|
|
22274
|
+
return { content: [{ type: "text", text: base64Text }] };
|
|
22275
|
+
}
|
|
22276
|
+
const approxKb = Math.ceil(base64Text.length / 1024).toLocaleString();
|
|
22277
|
+
const limitKb = Math.floor(
|
|
22278
|
+
MAX_ALLOWED_RESPONSE_CHARS / 1024
|
|
22279
|
+
).toLocaleString();
|
|
22280
|
+
return executeErrorResult(
|
|
22281
|
+
`Binary (base64) result is ~${approxKb} KB, too large to return intact (limit ~${limitKb} KB); truncating it would corrupt the base64. Process the bytes inside your ExecuteWixAPI script \u2014 decode, edit, and re-upload in the same call \u2014 and return only a small summary.`
|
|
22282
|
+
);
|
|
22283
|
+
}
|
|
22218
22284
|
return {
|
|
22219
22285
|
content: [
|
|
22220
22286
|
{ type: "text", text: truncateResponse(data.result, maxResponseSize) }
|
|
22221
22287
|
]
|
|
22222
22288
|
};
|
|
22223
22289
|
}
|
|
22290
|
+
function base64PayloadText(result) {
|
|
22291
|
+
if (result !== null && typeof result === "object" && result.encoding === "base64" && typeof result.data === "string") {
|
|
22292
|
+
return JSON.stringify(result);
|
|
22293
|
+
}
|
|
22294
|
+
return null;
|
|
22295
|
+
}
|
|
22224
22296
|
function addExecuteTool(server, options) {
|
|
22225
22297
|
const {
|
|
22226
22298
|
codeModeBaseUrl,
|
|
@@ -22252,6 +22324,14 @@ function addExecuteTool(server, options) {
|
|
|
22252
22324
|
visitor ? "Whether this code creates, updates, deletes, publishes, imports, uploads, or otherwise mutates site data on the visitor\u2019s behalf. Set this to true for create/update/delete/bulk create/import/upload calls even if the reason is inspection, verification, or response-shape discovery. Read-only GET/query/list/search calls can use false." : "Whether this code creates, updates, deletes, publishes, installs, imports, uploads, or otherwise mutates Wix data or site/account state. Set this to true for create/update/delete/bulk create/import/upload calls even if the reason is inspection, verification, or response-shape discovery. Read-only GET/query/list/search calls can use false."
|
|
22253
22325
|
),
|
|
22254
22326
|
sourceDocUrls: external_exports.array(external_exports.string()).describe(d.sourceDocUrls),
|
|
22327
|
+
files: external_exports.string().optional().describe(
|
|
22328
|
+
[
|
|
22329
|
+
"Optional bundle of raw file contents for uploads, so file bytes never go through the `code` JavaScript (no escaping, no string-literal/`${}`/backtick pitfalls).",
|
|
22330
|
+
"Format: one or more sections, each a line `=== FILE: <path> ===` on its own, followed by that file's raw text content; `<path>` is the path relative to the site root (e.g. `index.html`, `assets/app.js`). Example: `=== FILE: index.html ===\\n<!doctype html>...\\n=== FILE: assets/app.js ===\\nconsole.log(1)`.",
|
|
22331
|
+
'In `code`, the parsed files are the `files` global (array of `{ path, content }`), and `wix.multipart(files)` returns `{ body, contentType }` for a `multipart/form-data` upload \u2014 e.g. `const mp = wix.multipart(); await wix.request({ scope: "account", method: "POST", url, headers: { "Content-Type": mp.contentType }, body: mp.body });`.',
|
|
22332
|
+
"Text files only; for binary assets pass bytes as a base64 request body instead. A file whose content itself contains a line exactly like the delimiter is not supported."
|
|
22333
|
+
].join(" ")
|
|
22334
|
+
),
|
|
22255
22335
|
// Visitor mode takes an agent-supplied token and pins the site; account/site
|
|
22256
22336
|
// mode lets the agent pick the site.
|
|
22257
22337
|
...visitor ? {
|
|
@@ -22266,7 +22346,7 @@ function addExecuteTool(server, options) {
|
|
|
22266
22346
|
},
|
|
22267
22347
|
{ readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
22268
22348
|
async (rawArgs, extra) => {
|
|
22269
|
-
const { code, reason, hasMutations, sourceDocUrls, visitorToken } = rawArgs;
|
|
22349
|
+
const { code, reason, hasMutations, sourceDocUrls, visitorToken, files } = rawArgs;
|
|
22270
22350
|
const siteId = visitor ? visitor.siteId : rawArgs.siteId;
|
|
22271
22351
|
logger.log(
|
|
22272
22352
|
`[ExecuteWixAPI]${visitor ? " (visitor)" : ""} Executing code (${code.length} chars), siteId=${siteId ?? "none"}, hasMutations=${hasMutations}, sourceDocUrls=${JSON.stringify(sourceDocUrls)}, reason="${reason}"`
|
|
@@ -22311,7 +22391,8 @@ function addExecuteTool(server, options) {
|
|
|
22311
22391
|
siteToken: visitor ? visitorToken : siteHeaders?.Authorization,
|
|
22312
22392
|
accountToken: visitor ? visitorToken : accountHeaders?.Authorization,
|
|
22313
22393
|
accountId: accountHeaders?.["wix-account-id"],
|
|
22314
|
-
siteAuthContext
|
|
22394
|
+
siteAuthContext,
|
|
22395
|
+
...files ? { files } : {}
|
|
22315
22396
|
},
|
|
22316
22397
|
visitor ? visitorToken : accountHeaders?.Authorization ?? siteHeaders?.Authorization,
|
|
22317
22398
|
codeModeFetch
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { logger } from '../infra/logger.js';
|
|
3
3
|
import { defaultToolDescriptions } from '../config/default-config.js';
|
|
4
4
|
import { truncateResponse } from './truncate-response.js';
|
|
5
|
+
import { MAX_ALLOWED_RESPONSE_CHARS } from '../infra/response-size-limits.js';
|
|
5
6
|
import { validateExecuteCode } from './validation.js';
|
|
6
7
|
import { desc } from '../config/param-descriptions.js';
|
|
7
8
|
import { rethrowIfUnauthorized } from '../auth/index.js';
|
|
@@ -176,12 +177,39 @@ function formatExecuteResult(data, maxResponseSize, reportBi) {
|
|
|
176
177
|
? data.result.length
|
|
177
178
|
: JSON.stringify(data.result)?.length
|
|
178
179
|
});
|
|
180
|
+
// A base64/binary payload (a downloaded zip, image, …) must never be sliced:
|
|
181
|
+
// a cut base64 string decodes to garbage, so the usual char-limit truncation
|
|
182
|
+
// would silently corrupt it. Return it whole up to the hard ceiling; past
|
|
183
|
+
// that, fail cleanly rather than hand back a broken blob.
|
|
184
|
+
const base64Text = base64PayloadText(data.result);
|
|
185
|
+
if (base64Text !== null) {
|
|
186
|
+
if (base64Text.length <= MAX_ALLOWED_RESPONSE_CHARS) {
|
|
187
|
+
return { content: [{ type: 'text', text: base64Text }] };
|
|
188
|
+
}
|
|
189
|
+
const approxKb = Math.ceil(base64Text.length / 1024).toLocaleString();
|
|
190
|
+
const limitKb = Math.floor(MAX_ALLOWED_RESPONSE_CHARS / 1024).toLocaleString();
|
|
191
|
+
return executeErrorResult(`Binary (base64) result is ~${approxKb} KB, too large to return intact (limit ~${limitKb} KB); truncating it would corrupt the base64. Process the bytes inside your ExecuteWixAPI script — decode, edit, and re-upload in the same call — and return only a small summary.`);
|
|
192
|
+
}
|
|
179
193
|
return {
|
|
180
194
|
content: [
|
|
181
195
|
{ type: 'text', text: truncateResponse(data.result, maxResponseSize) }
|
|
182
196
|
]
|
|
183
197
|
};
|
|
184
198
|
}
|
|
199
|
+
// A base64 payload the caller returned as a WixResponse carrying
|
|
200
|
+
// `encoding: "base64"` — the shape `wix.request` produces for a binary body.
|
|
201
|
+
// We key off that explicit marker, never a content heuristic: a long string in
|
|
202
|
+
// the base64 alphabet can be ordinary text (e.g. "x".repeat(30000)) and must
|
|
203
|
+
// still truncate normally. Returns the text to keep intact, or null otherwise.
|
|
204
|
+
function base64PayloadText(result) {
|
|
205
|
+
if (result !== null &&
|
|
206
|
+
typeof result === 'object' &&
|
|
207
|
+
result.encoding === 'base64' &&
|
|
208
|
+
typeof result.data === 'string') {
|
|
209
|
+
return JSON.stringify(result);
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
185
213
|
function addExecuteTool(server, options) {
|
|
186
214
|
const { codeModeBaseUrl, codeModeFetch, authStrategy, visitor, toolDescriptions, maxResponseSize } = options;
|
|
187
215
|
const executeUrl = `${codeModeBaseUrl}/api/code-mode/execute`;
|
|
@@ -208,6 +236,15 @@ function addExecuteTool(server, options) {
|
|
|
208
236
|
? 'Whether this code creates, updates, deletes, publishes, imports, uploads, or otherwise mutates site data on the visitor’s behalf. Set this to true for create/update/delete/bulk create/import/upload calls even if the reason is inspection, verification, or response-shape discovery. Read-only GET/query/list/search calls can use false.'
|
|
209
237
|
: 'Whether this code creates, updates, deletes, publishes, installs, imports, uploads, or otherwise mutates Wix data or site/account state. Set this to true for create/update/delete/bulk create/import/upload calls even if the reason is inspection, verification, or response-shape discovery. Read-only GET/query/list/search calls can use false.'),
|
|
210
238
|
sourceDocUrls: z.array(z.string()).describe(d.sourceDocUrls),
|
|
239
|
+
files: z
|
|
240
|
+
.string()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe([
|
|
243
|
+
'Optional bundle of raw file contents for uploads, so file bytes never go through the `code` JavaScript (no escaping, no string-literal/`${}`/backtick pitfalls).',
|
|
244
|
+
"Format: one or more sections, each a line `=== FILE: <path> ===` on its own, followed by that file's raw text content; `<path>` is the path relative to the site root (e.g. `index.html`, `assets/app.js`). Example: `=== FILE: index.html ===\\n<!doctype html>...\\n=== FILE: assets/app.js ===\\nconsole.log(1)`.",
|
|
245
|
+
'In `code`, the parsed files are the `files` global (array of `{ path, content }`), and `wix.multipart(files)` returns `{ body, contentType }` for a `multipart/form-data` upload — e.g. `const mp = wix.multipart(); await wix.request({ scope: "account", method: "POST", url, headers: { "Content-Type": mp.contentType }, body: mp.body });`.',
|
|
246
|
+
'Text files only; for binary assets pass bytes as a base64 request body instead. A file whose content itself contains a line exactly like the delimiter is not supported.'
|
|
247
|
+
].join(' ')),
|
|
211
248
|
// Visitor mode takes an agent-supplied token and pins the site; account/site
|
|
212
249
|
// mode lets the agent pick the site.
|
|
213
250
|
...(visitor
|
|
@@ -226,7 +263,7 @@ function addExecuteTool(server, options) {
|
|
|
226
263
|
// Partial: duck-typed servers (the toolkit's capturing server, test
|
|
227
264
|
// doubles) build their own extra and may not supply every field.
|
|
228
265
|
extra) => {
|
|
229
|
-
const { code, reason, hasMutations, sourceDocUrls, visitorToken } = rawArgs;
|
|
266
|
+
const { code, reason, hasMutations, sourceDocUrls, visitorToken, files } = rawArgs;
|
|
230
267
|
// In visitor mode the site is pinned; otherwise it comes from the param.
|
|
231
268
|
const siteId = visitor ? visitor.siteId : rawArgs.siteId;
|
|
232
269
|
logger.log(`[ExecuteWixAPI]${visitor ? ' (visitor)' : ''} Executing code (${code.length} chars), siteId=${siteId ?? 'none'}, hasMutations=${hasMutations}, sourceDocUrls=${JSON.stringify(sourceDocUrls)}, reason="${reason}"`);
|
|
@@ -290,7 +327,8 @@ function addExecuteTool(server, options) {
|
|
|
290
327
|
? visitorToken
|
|
291
328
|
: accountHeaders?.Authorization,
|
|
292
329
|
accountId: accountHeaders?.['wix-account-id'],
|
|
293
|
-
siteAuthContext
|
|
330
|
+
siteAuthContext,
|
|
331
|
+
...(files ? { files } : {})
|
|
294
332
|
}, visitor
|
|
295
333
|
? visitorToken
|
|
296
334
|
: (accountHeaders?.Authorization ?? siteHeaders?.Authorization), codeModeFetch);
|
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
* single unknown enum member and rejected with HTTP 400.
|
|
9
9
|
*/
|
|
10
10
|
export declare const ARRAY_QUERY_PARAMS_RULE = "Repeat array query params once per value: `?fieldsets=URL&fieldsets=RICH_CONTENT`. Do NOT comma-join values (`fieldsets=URL,RICH_CONTENT`), do NOT leave a trailing comma, and do NOT use a value the method schema does not list, even if a sibling method accepts it (draft posts have no `SEO` fieldset).";
|
|
11
|
+
export declare const WQL_FILTER_RULE = "Query and search filters are written in WQL: each field takes a single operator, so combine conditions with the logical operators \u2014 `$and` and `$or` take an array of expressions, `$not` takes one, and they can nest.";
|
|
11
12
|
//# sourceMappingURL=api-request-rules.d.ts.map
|
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
* single unknown enum member and rejected with HTTP 400.
|
|
9
9
|
*/
|
|
10
10
|
export const ARRAY_QUERY_PARAMS_RULE = 'Repeat array query params once per value: `?fieldsets=URL&fieldsets=RICH_CONTENT`. Do NOT comma-join values (`fieldsets=URL,RICH_CONTENT`), do NOT leave a trailing comma, and do NOT use a value the method schema does not list, even if a sibling method accepts it (draft posts have no `SEO` fieldset).';
|
|
11
|
+
export const WQL_FILTER_RULE = 'Query and search filters are written in WQL: each field takes a single operator, so combine conditions with the logical operators — `$and` and `$or` take an array of expressions, `$not` takes one, and they can nest.';
|
|
11
12
|
//# sourceMappingURL=api-request-rules.js.map
|
|
@@ -80,7 +80,7 @@ export const SITE_CREATION_COPY = {
|
|
|
80
80
|
const SITE_GUIDANCE = dedent `
|
|
81
81
|
A new-site request has two things to settle: what the site is for, and how to build it. The how is AI generation vs. starting from a template, so naming templates, a template id or Wix Studio (Wix Studio is also the template side) already settles it.
|
|
82
82
|
|
|
83
|
-
One case skips all of this: the user ALREADY HAS the finished site as files — hand-written HTML/CSS/JS, a zip, or the export of an AI builder. There is nothing to build, so don't offer AI or templates and don't call any site-creation tool.
|
|
83
|
+
One case skips all of this: the user ALREADY HAS the finished site as files — hand-written HTML/CSS/JS, a zip, or the export of an AI builder. There is nothing to build, so don't offer AI or templates and don't call any site-creation tool. For a full agentic flow, read the recipe first and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it walks you through creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply (you can't reach the file bytes, or something fails), fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp — they drag the files in and get a live Wix-hosted site. Give that URL exactly as written, keeping the utm_campaign=mcp parameter.
|
|
84
84
|
|
|
85
85
|
Settle what the site is for first: its purpose, topic, business type, audience, or main visitor action. Naming a method is not a description of the site, and neither is the business or industry on its own.
|
|
86
86
|
If you can't tell yet, ask the preferred question below and nothing else — one question, no creation-method question in the same reply, and no internal tool names.
|
|
@@ -142,18 +142,56 @@ const ACCOUNT_SITE_EXECUTE_SCOPE = dedent `
|
|
|
142
142
|
siteId?: string;
|
|
143
143
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
144
144
|
url: string;
|
|
145
|
+
// Plain object → JSON. A string is sent verbatim (set your own Content-Type,
|
|
146
|
+
// e.g. multipart/form-data). A Uint8Array/ArrayBuffer is sent as raw bytes
|
|
147
|
+
// for binary uploads (set your own Content-Type).
|
|
145
148
|
body?: unknown;
|
|
146
149
|
headers?: Record<string, string>;
|
|
150
|
+
// "base64" returns the response body as a base64 string (data), with
|
|
151
|
+
// encoding: "base64" on the result — use it for binary downloads (zip,
|
|
152
|
+
// image, pdf); decoding those as text corrupts them. Binary content types
|
|
153
|
+
// are returned as base64 automatically even without this. Decode, edit and
|
|
154
|
+
// re-upload binary WITHIN this script and return only a small summary. If you
|
|
155
|
+
// must return the bytes, return the wix.request response object itself (so
|
|
156
|
+
// encoding: "base64" is preserved and it isn't truncated); a bare base64
|
|
157
|
+
// string you return is treated as plain text and may be truncated.
|
|
158
|
+
responseType?: "base64";
|
|
147
159
|
}
|
|
148
160
|
interface WixResponse<T = unknown> {
|
|
149
161
|
status: number;
|
|
150
162
|
data: T;
|
|
163
|
+
encoding?: "base64";
|
|
151
164
|
json(): Promise<T>;
|
|
152
165
|
}
|
|
153
166
|
declare const wix: {
|
|
154
167
|
request<T = unknown>(options: WixRequestOptions): Promise<WixResponse<T>>;
|
|
168
|
+
// Build a multipart/form-data body from files (defaults to the \`files\`
|
|
169
|
+
// global). Each file becomes a part named fieldName (default "files") with
|
|
170
|
+
// its path as the filename. Pass body + contentType to wix.request.
|
|
171
|
+
multipart(
|
|
172
|
+
files?: Array<{ path: string; content: string; contentType?: string }>,
|
|
173
|
+
options?: { fieldName?: string }
|
|
174
|
+
): { body: string; contentType: string };
|
|
155
175
|
};
|
|
156
176
|
declare const siteId: string | undefined;
|
|
177
|
+
// Files from the ExecuteWixAPI \`files\` param (raw content, no escaping).
|
|
178
|
+
declare const files: Array<{ path: string; content: string }>;
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
FILE UPLOADS: pass file bodies via the \`files\` param (not inside \`code\` — no escaping), then build the multipart body with \`wix.multipart()\`:
|
|
182
|
+
\`\`\`javascript
|
|
183
|
+
// files param: "=== FILE: index.html ===\\n<!doctype html>...\\n=== FILE: app.js ===\\nconsole.log(1)"
|
|
184
|
+
async () => {
|
|
185
|
+
const mp = wix.multipart(); // parts named "files", filename = each path
|
|
186
|
+
const res = await wix.request({
|
|
187
|
+
scope: "account",
|
|
188
|
+
method: "POST",
|
|
189
|
+
url: "https://www.wixapis.com/.../upload",
|
|
190
|
+
headers: { "Content-Type": mp.contentType },
|
|
191
|
+
body: mp.body
|
|
192
|
+
});
|
|
193
|
+
return res.data;
|
|
194
|
+
}
|
|
157
195
|
\`\`\`
|
|
158
196
|
`;
|
|
159
197
|
const EXECUTE_WIX_API_WQL = dedent `
|
|
@@ -283,6 +321,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
|
|
|
283
321
|
ReadFullDocsArticle: dedent `
|
|
284
322
|
Fetches the full Wix docs article or method article with code examples for using the method.
|
|
285
323
|
Docs articles looks like this: https://dev.wix.com/docs/... and they can either be general docs articles or method articles.
|
|
324
|
+
Take the URL from the user's message or an earlier tool result. To reach an article you have no URL for, search or browse the docs first.
|
|
286
325
|
For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include ?apiView=SDK.
|
|
287
326
|
${reminder}
|
|
288
327
|
`,
|
|
@@ -415,7 +454,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
|
|
|
415
454
|
`,
|
|
416
455
|
'import-claude-design-from-url': dedent `
|
|
417
456
|
Import a design created in Claude's design tool into Wix. Takes the signed claudeusercontent.com URL Claude Design produces for its self-contained HTML bundle (all images, fonts, and styles inlined); any other host is rejected. Creates a live Wix-hosted site and returns its URL.
|
|
418
|
-
NOT for site files the user provides themselves (their own HTML/CSS/JS, a zip, another builder's export) — for those, don't call this tool:
|
|
457
|
+
NOT for site files the user provides themselves (their own HTML/CSS/JS, a zip, another builder's export) — for those, don't call this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp, where they upload the files and get a live hosted site. Give that URL exactly as written, keeping the utm_campaign=mcp parameter.
|
|
419
458
|
`,
|
|
420
459
|
RunWixCliCommand: dedent `
|
|
421
460
|
Executes Wix CLI commands for Wix app development and management.
|
|
@@ -842,7 +881,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
|
|
|
842
881
|
CreateWixBusinessGuide: dedent `
|
|
843
882
|
Provides comprehensive documentation for creating a new Wix business (site, app, etc.).
|
|
844
883
|
Call this ONLY when the user explicitly asks for a headless site, a Wix Classic Editor site, or manual/advanced account/API-based site creation — these don't need the site's purpose first. For normal site-creation ${SITE_CREATION_COPY.normalCreationRoute.nonWidget}; for a specific template id, call \`CreateSiteFromTemplate\`.
|
|
845
|
-
Exception, even when the user says "headless": if they ALREADY HAVE the finished site as files (their own HTML/CSS/JS, a zip, an AI-builder export) and want those files hosted, don't call this tool —
|
|
884
|
+
Exception, even when the user says "headless": if they ALREADY HAVE the finished site as files (their own HTML/CSS/JS, a zip, an AI-builder export) and want those files hosted, don't call this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, fall back to sending the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload the files and get a live hosted site (give that URL exactly, keeping the utm_campaign=mcp parameter). This guide's CLI flow is for BUILDING a headless frontend, not for hosting ready-made files.
|
|
846
885
|
|
|
847
886
|
## What This Tool Contains
|
|
848
887
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ALLOWED_HOSTNAMES } from '../api-call/validation.js';
|
|
2
|
-
import { ARRAY_QUERY_PARAMS_RULE } from './api-request-rules.js';
|
|
2
|
+
import { ARRAY_QUERY_PARAMS_RULE, WQL_FILTER_RULE } from './api-request-rules.js';
|
|
3
3
|
/**
|
|
4
4
|
* Centralized parameter descriptions organized by tool.
|
|
5
5
|
* Each tool has all its parameter descriptions in one place.
|
|
@@ -15,7 +15,10 @@ export const paramDescriptions = {
|
|
|
15
15
|
ARRAY_QUERY_PARAMS_RULE
|
|
16
16
|
].join('\n'),
|
|
17
17
|
method: 'The HTTP method to use for the API call (e.g. GET, POST, PUT, DELETE)',
|
|
18
|
-
body:
|
|
18
|
+
body: [
|
|
19
|
+
'The request body as a JSON object or array with all the required fields and values, including nested objects. Pass the actual value (object or array), NOT a JSON string. YOU MUST NEVER MAKE UP A BODY - the body should be based on the conversation context, i.e from the user prompt OR got into the conversation context by the "ReadFullDocsArticle" tool OR by the "SearchWixAPISpec" tool OR by the "ReadFullDocsMethodSchema" tool - i.e based on the API docs, a relevant recipe you read (preferably), a code example you found in the docs, a schema you read etc.. YOU MUST NEVER ASSUME YOU KNOW WHAT THE BODY SCHEMA IS WITHOUT CONCRETE EXAMPLES OR SCHEMA DEFINITIONS FROM THE CONVERSATION CONTEXT. Prefer reading relevant recipes if you have them in context for understand the body schema for API calls.',
|
|
20
|
+
WQL_FILTER_RULE
|
|
21
|
+
].join('\n'),
|
|
19
22
|
reason: 'One sentence explaining the original user request and why you are calling this API to complete it.',
|
|
20
23
|
sourceDocUrl: [
|
|
21
24
|
'The URL of the documentation or recipe where you found this API endpoint.',
|
|
@@ -169,17 +169,20 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
|
|
|
169
169
|
// return '';
|
|
170
170
|
// });
|
|
171
171
|
const baseContentsPromise = Promise.all(baseDocsWithoutRecipes.map(fetchDoc));
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
172
|
+
// Filtered lists still come from the portal. The full management index is
|
|
173
|
+
// authored alongside the recipes and is fetched like any other article,
|
|
174
|
+
// using its commit version when a PR override is configured.
|
|
175
|
+
const skillsContentPromise = skillsTags?.length
|
|
176
|
+
? getSkillsByPortal(httpClient, {
|
|
177
|
+
portalId: REST_PORTAL_ID,
|
|
178
|
+
...(repo && { repo }),
|
|
179
|
+
...(commitHash && { commitHash }),
|
|
180
|
+
tags: skillsTags
|
|
181
|
+
}).catch((err) => {
|
|
182
|
+
console.error('[WixREADME] Failed to fetch skills by portal:', err);
|
|
183
|
+
return null;
|
|
184
|
+
})
|
|
185
|
+
: fetchDoc({ action: 'include', url: RECIPES_INDEX_URL }).then((doc) => doc?.text ?? null);
|
|
183
186
|
// Auto-inject site context. With no site targeted this is the account-wide
|
|
184
187
|
// view: single-site accounts "just work", and accounts with more than the
|
|
185
188
|
// cap show the first N sites plus a note to use GetSiteContext for the rest.
|
|
@@ -86,11 +86,11 @@ export function addImportClaudeDesignTool(server, authStrategy, options) {
|
|
|
86
86
|
designUrl = new URL(args.url);
|
|
87
87
|
}
|
|
88
88
|
catch {
|
|
89
|
-
return errorResult(
|
|
89
|
+
return errorResult("Invalid design URL — this tool only accepts the claudeusercontent.com URL produced by Claude Design. For site files the user provided themselves, do not retry this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, send the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload them and get a live hosted site.");
|
|
90
90
|
}
|
|
91
91
|
if (designUrl.protocol !== 'https:' ||
|
|
92
92
|
!designUrl.hostname.endsWith(CLAUDE_USER_CONTENT_HOST_SUFFIX)) {
|
|
93
|
-
return errorResult(
|
|
93
|
+
return errorResult("Invalid design URL — this tool only accepts the claudeusercontent.com URL produced by Claude Design. For site files the user provided themselves, do not retry this tool. For a full agentic flow, read the recipe and publish the files yourself: https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md — it covers creating the site and uploading the files end to end. If you can't follow it, or it doesn't apply, send the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload them and get a live hosted site.");
|
|
94
94
|
}
|
|
95
95
|
// Resolve the caller's account identity BEFORE the design fetch — the
|
|
96
96
|
// design URL is single-use, so an unauthenticated caller must fail
|
package/build/dts/tool-utils.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
const REST_METHOD_CONTRACT_RETRY_HINT = 'Before retrying, make sure you have everything needed for a correct call: the right endpoint URL, HTTP method, request body shape, and response shape. ' +
|
|
2
2
|
'Use the SearchWixRESTDocumentation tool or BrowseWixRESTDocsMenu tool to find the correct method or relevant related articles, the ReadFullDocsArticle tool to read the method description and all code examples, and the SearchWixAPISpec tool for precise field names and schema details.';
|
|
3
|
+
const SINGLE_OPERATOR_PER_FIELD_HINT = 'WQL takes one operator per field object. Split the conditions into separate expressions under the matching logical operator — `{"$and": [{"field": {"$gte": ...}}, {"field": {"$lte": ...}}]}` — and retry without re-searching the docs.';
|
|
4
|
+
const FIELD_CAPABILITIES_HINT = 'This endpoint declares its filterable and sortable fields with their operators. Read the map with SearchWixAPISpec (`queryMethodData.queryFieldsCapabilitiesMap`, or `searchMethodData.searchFieldsCapabilitiesMap` on search methods) and use only what it lists; otherwise fetch a bounded page and filter or sort the results yourself.';
|
|
5
|
+
const getRetryHint = (errorDetails) => {
|
|
6
|
+
if (!/Filter contains unsupported operator/i.test(errorDetails)) {
|
|
7
|
+
return REST_METHOD_CONTRACT_RETRY_HINT;
|
|
8
|
+
}
|
|
9
|
+
// The API reports a field object with several operators as one unknown operator whose name is that object.
|
|
10
|
+
return /"operator":"\{/.test(errorDetails)
|
|
11
|
+
? SINGLE_OPERATOR_PER_FIELD_HINT
|
|
12
|
+
: FIELD_CAPABILITIES_HINT;
|
|
13
|
+
};
|
|
3
14
|
export const DEFAULT_OPENAI_SECURITY_SCHEMES = [
|
|
4
15
|
{ type: 'oauth2', scopes: ['offline_access'] }
|
|
5
16
|
];
|
|
@@ -28,7 +39,7 @@ export const buildErrorMessage = ({ status, statusText, requestId, responseData
|
|
|
28
39
|
]
|
|
29
40
|
.filter((str) => !!str)
|
|
30
41
|
.join('\n');
|
|
31
|
-
return `${baseMessage}\n\n${
|
|
42
|
+
return `${baseMessage}\n\n${getRetryHint(String(errorDetails))}`;
|
|
32
43
|
};
|
|
33
44
|
export const handleWixAPIResponse = async (response) => {
|
|
34
45
|
const requestId = response.headers?.['x-wix-request-id'];
|