@wix/mcp 1.0.85 → 1.0.87

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.
@@ -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. Send them 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
+ 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,12 +17815,25 @@ 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: {
@@ -17958,6 +17972,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
17958
17972
  ReadFullDocsArticle: dedent_default`
17959
17973
  Fetches the full Wix docs article or method article with code examples for using the method.
17960
17974
  Docs articles looks like this: https://dev.wix.com/docs/... and they can either be general docs articles or method articles.
17975
+ 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
17976
  For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include ?apiView=SDK.
17962
17977
  ${reminder}
17963
17978
  `,
@@ -18090,7 +18105,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
18090
18105
  `,
18091
18106
  "import-claude-design-from-url": dedent_default`
18092
18107
  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: send 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.
18108
+ 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
18109
  `,
18095
18110
  RunWixCliCommand: dedent_default`
18096
18111
  Executes Wix CLI commands for Wix app development and management.
@@ -18517,7 +18532,7 @@ function buildToolDescriptions(includeReadme, includeImageUploadGuidance = true)
18517
18532
  CreateWixBusinessGuide: dedent_default`
18518
18533
  Provides comprehensive documentation for creating a new Wix business (site, app, etc.).
18519
18534
  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 — send them 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.
18535
+ 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
18536
 
18522
18537
  ## What This Tool Contains
18523
18538
 
@@ -18804,6 +18819,22 @@ function renderApps(apps, knownApps) {
18804
18819
  }
18805
18820
  return lines.join("\n");
18806
18821
  }
18822
+ function sanitizeCustomAppName(raw) {
18823
+ return raw.replace(/[\x00-\x1f\x7f]/g, " ").replace(/[`*_[\]|<>]/g, "").replace(/\s+/g, " ").trim().slice(0, 60);
18824
+ }
18825
+ function renderCustomAppsWithSkillsTools(apps) {
18826
+ const relevant = apps.filter((a) => a.appId && (a.hasSkills || a.hasTools)).sort(
18827
+ (a, b) => (a.appName ?? a.appId ?? "").localeCompare(b.appName ?? b.appId ?? "")
18828
+ );
18829
+ if (!relevant.length) return "";
18830
+ const lines = [];
18831
+ for (const app of relevant) {
18832
+ const sanitizedName = app.appName ? sanitizeCustomAppName(app.appName) : "";
18833
+ const name = sanitizedName || app.appId;
18834
+ lines.push(`- \`${name}\` (ID: \`${app.appId}\`)`);
18835
+ }
18836
+ return lines.join("\n");
18837
+ }
18807
18838
  function renderProperties(props) {
18808
18839
  const sections = [];
18809
18840
  const regionalFormat = [];
@@ -18866,6 +18897,15 @@ function renderSite(site, index, knownApps) {
18866
18897
  lines.push("");
18867
18898
  lines.push(appsContent);
18868
18899
  }
18900
+ const customAppsWithSkillsToolsContent = renderCustomAppsWithSkillsTools(
18901
+ site.customApps ?? []
18902
+ );
18903
+ if (customAppsWithSkillsToolsContent) {
18904
+ lines.push("");
18905
+ lines.push("### Custom Apps with skills and tools");
18906
+ lines.push("");
18907
+ lines.push(customAppsWithSkillsToolsContent);
18908
+ }
18869
18909
  lines.push("");
18870
18910
  lines.push("---");
18871
18911
  lines.push("");
@@ -19129,7 +19169,10 @@ var paramDescriptions = {
19129
19169
  ARRAY_QUERY_PARAMS_RULE
19130
19170
  ].join("\n"),
19131
19171
  method: "The HTTP method to use for the API call (e.g. GET, POST, PUT, DELETE)",
19132
- body: '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.',
19172
+ body: [
19173
+ '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.',
19174
+ WQL_FILTER_RULE
19175
+ ].join("\n"),
19133
19176
  reason: "One sentence explaining the original user request and why you are calling this API to complete it.",
19134
19177
  sourceDocUrl: [
19135
19178
  "The URL of the documentation or recipe where you found this API endpoint.",
@@ -19453,15 +19496,17 @@ Failed to fetch content: ${errorMsg}`
19453
19496
  const baseContentsPromise = Promise.all(
19454
19497
  baseDocsWithoutRecipes.map(fetchDoc)
19455
19498
  );
19456
- const skillsContentPromise = getSkillsByPortal(httpClient, {
19499
+ const skillsContentPromise = skillsTags?.length ? getSkillsByPortal(httpClient, {
19457
19500
  portalId: REST_PORTAL_ID,
19458
19501
  ...repo && { repo },
19459
19502
  ...commitHash && { commitHash },
19460
- ...skillsTags && skillsTags.length > 0 && { tags: skillsTags }
19503
+ tags: skillsTags
19461
19504
  }).catch((err) => {
19462
19505
  console.error("[WixREADME] Failed to fetch skills by portal:", err);
19463
19506
  return null;
19464
- });
19507
+ }) : fetchDoc({ action: "include", url: RECIPES_INDEX_URL }).then(
19508
+ (doc) => doc?.text ?? null
19509
+ );
19465
19510
  const filter = {};
19466
19511
  if (siteId) filter.siteId = siteId;
19467
19512
  else if (siteName) filter.siteName = siteName;
@@ -19657,7 +19702,9 @@ function getErrorMessage(error) {
19657
19702
  }
19658
19703
  function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
19659
19704
  const message = getErrorMessage(error);
19660
- if (!message.includes("404") || !articleUrl.includes("/api-reference/")) {
19705
+ const { pathname } = new URL(articleUrl);
19706
+ const isMenuFallbackUrl = articleUrl.includes("/api-reference/") || pathname.startsWith("/docs/velo");
19707
+ if (!message.includes("404") || !isMenuFallbackUrl) {
19661
19708
  return false;
19662
19709
  }
19663
19710
  return !["This resource is deprecated", "No schema found"].some(
@@ -20310,7 +20357,7 @@ var addDocsTools = (server, allowedTools = [
20310
20357
  content: [
20311
20358
  {
20312
20359
  type: "text",
20313
- text: `Note: "${articleUrl}" is a documentation category, not an article. Showing its menu contents below. For category URLs, use BrowseWixRESTDocsMenu directly next time.
20360
+ text: `Note: "${articleUrl}" is not an article. Showing the closest documentation menu below; read the article you need from its links. For category URLs, use BrowseWixRESTDocsMenu directly next time.
20314
20361
 
20315
20362
  ${text}`
20316
20363
  }
@@ -20402,7 +20449,7 @@ ${text}`
20402
20449
  content: [
20403
20450
  {
20404
20451
  type: "text",
20405
- text: `Note: "${articleUrl}" is a documentation category, not an article. Showing its menu contents below. For category URLs, use BrowseWixRESTDocsMenu directly next time.
20452
+ text: `Note: "${articleUrl}" is not an article. Showing the closest documentation menu below; read the article you need from its links. For category URLs, use BrowseWixRESTDocsMenu directly next time.
20406
20453
 
20407
20454
  ${text}`
20408
20455
  }
@@ -20498,6 +20545,14 @@ var import_http_client2 = __toESM(require_index_node(), 1);
20498
20545
 
20499
20546
  // src/tool-utils.ts
20500
20547
  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.";
20548
+ 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.';
20549
+ 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.";
20550
+ var getRetryHint = (errorDetails) => {
20551
+ if (!/Filter contains unsupported operator/i.test(errorDetails)) {
20552
+ return REST_METHOD_CONTRACT_RETRY_HINT;
20553
+ }
20554
+ return /"operator":"\{/.test(errorDetails) ? SINGLE_OPERATOR_PER_FIELD_HINT : FIELD_CAPABILITIES_HINT;
20555
+ };
20501
20556
  var safeParseJSON = (text) => {
20502
20557
  try {
20503
20558
  return JSON.parse(text);
@@ -20521,7 +20576,7 @@ var buildErrorMessage = ({
20521
20576
  ].filter((str) => !!str).join("\n");
20522
20577
  return `${baseMessage}
20523
20578
 
20524
- ${REST_METHOD_CONTRACT_RETRY_HINT}`;
20579
+ ${getRetryHint(String(errorDetails))}`;
20525
20580
  };
20526
20581
  var handleWixAPIResponse = async (response) => {
20527
20582
  const requestId = response.headers?.["x-wix-request-id"];
@@ -22188,12 +22243,31 @@ function formatExecuteResult(data, maxResponseSize, reportBi) {
22188
22243
  reportBi?.({
22189
22244
  originalLength: typeof data.result === "string" ? data.result.length : JSON.stringify(data.result)?.length
22190
22245
  });
22246
+ const base64Text = base64PayloadText(data.result);
22247
+ if (base64Text !== null) {
22248
+ if (base64Text.length <= MAX_ALLOWED_RESPONSE_CHARS) {
22249
+ return { content: [{ type: "text", text: base64Text }] };
22250
+ }
22251
+ const approxKb = Math.ceil(base64Text.length / 1024).toLocaleString();
22252
+ const limitKb = Math.floor(
22253
+ MAX_ALLOWED_RESPONSE_CHARS / 1024
22254
+ ).toLocaleString();
22255
+ return executeErrorResult(
22256
+ `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.`
22257
+ );
22258
+ }
22191
22259
  return {
22192
22260
  content: [
22193
22261
  { type: "text", text: truncateResponse(data.result, maxResponseSize) }
22194
22262
  ]
22195
22263
  };
22196
22264
  }
22265
+ function base64PayloadText(result) {
22266
+ if (result !== null && typeof result === "object" && result.encoding === "base64" && typeof result.data === "string") {
22267
+ return JSON.stringify(result);
22268
+ }
22269
+ return null;
22270
+ }
22197
22271
  function addExecuteTool(server, options) {
22198
22272
  const {
22199
22273
  codeModeBaseUrl,
@@ -22395,7 +22469,25 @@ function extractText(result) {
22395
22469
  return result.content.filter((content) => content.type === "text" && content.text != null).map((content) => content.text).join("\n");
22396
22470
  }
22397
22471
  var TOOL_RESULT_PREVIEW_MAX_CHARS = 4e3;
22398
- function formatToolResultAdditionalFields(result, extras) {
22472
+ var CODEX_OUTPUT_BUDGET_BYTES = 4e4;
22473
+ function getCodexTruncation(resultText, client) {
22474
+ const isCodex = [client?.name, client?.userAgent].some(
22475
+ (identity) => identity?.toLowerCase().includes("codex")
22476
+ );
22477
+ if (!isCodex) {
22478
+ return void 0;
22479
+ }
22480
+ const resultBytes = new TextEncoder().encode(resultText).length;
22481
+ if (resultBytes <= CODEX_OUTPUT_BUDGET_BYTES) {
22482
+ return void 0;
22483
+ }
22484
+ return {
22485
+ resultBytes,
22486
+ budgetBytes: CODEX_OUTPUT_BUDGET_BYTES,
22487
+ truncatedBytes: resultBytes - CODEX_OUTPUT_BUDGET_BYTES
22488
+ };
22489
+ }
22490
+ function formatToolResultAdditionalFields(result, extras, client) {
22399
22491
  const resultText = extractText(result);
22400
22492
  const contentTypes = [
22401
22493
  ...new Set(
@@ -22412,6 +22504,7 @@ function formatToolResultAdditionalFields(result, extras) {
22412
22504
  resultTextLength: resultText.length,
22413
22505
  resultPreviewTruncated: resultText.length > TOOL_RESULT_PREVIEW_MAX_CHARS,
22414
22506
  resultContentTypes: contentTypes,
22507
+ codexTruncation: getCodexTruncation(resultText, client),
22415
22508
  ...extras
22416
22509
  });
22417
22510
  }
@@ -22667,10 +22760,14 @@ function createWixToolkit(options) {
22667
22760
  if (!toolSucceeded) {
22668
22761
  errorBody = extractText(cbResult);
22669
22762
  } else {
22670
- additionalFields = formatToolResultAdditionalFields(cbResult, {
22671
- returnedRows: toolBiParams.returnedRows,
22672
- originalLength: toolBiParams.originalLength
22673
- });
22763
+ additionalFields = formatToolResultAdditionalFields(
22764
+ cbResult,
22765
+ {
22766
+ returnedRows: toolBiParams.returnedRows,
22767
+ originalLength: toolBiParams.originalLength
22768
+ },
22769
+ { name: clientName }
22770
+ );
22674
22771
  }
22675
22772
  if (!isDevEnvironment) {
22676
22773
  if (toolSucceeded) {
@@ -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`;
@@ -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. Send them 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.
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,12 +142,25 @@ 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: {
@@ -283,6 +296,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
283
296
  ReadFullDocsArticle: dedent `
284
297
  Fetches the full Wix docs article or method article with code examples for using the method.
285
298
  Docs articles looks like this: https://dev.wix.com/docs/... and they can either be general docs articles or method articles.
299
+ 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
300
  For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include ?apiView=SDK.
287
301
  ${reminder}
288
302
  `,
@@ -415,7 +429,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
415
429
  `,
416
430
  'import-claude-design-from-url': dedent `
417
431
  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: send 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.
432
+ 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
433
  `,
420
434
  RunWixCliCommand: dedent `
421
435
  Executes Wix CLI commands for Wix app development and management.
@@ -842,7 +856,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
842
856
  CreateWixBusinessGuide: dedent `
843
857
  Provides comprehensive documentation for creating a new Wix business (site, app, etc.).
844
858
  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 — send them 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.
859
+ 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
860
 
847
861
  ## What This Tool Contains
848
862
 
@@ -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: '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.',
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.',
@@ -47,7 +47,9 @@ function getErrorMessage(error) {
47
47
  }
48
48
  function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
49
49
  const message = getErrorMessage(error);
50
- if (!message.includes('404') || !articleUrl.includes('/api-reference/')) {
50
+ const { pathname } = new URL(articleUrl);
51
+ const isMenuFallbackUrl = articleUrl.includes('/api-reference/') || pathname.startsWith('/docs/velo');
52
+ if (!message.includes('404') || !isMenuFallbackUrl) {
51
53
  return false;
52
54
  }
53
55
  return !['This resource is deprecated', 'No schema found'].some((knownArticleError) => message.includes(knownArticleError));
@@ -671,7 +673,7 @@ export const addDocsTools = (server, allowedTools = [
671
673
  content: [
672
674
  {
673
675
  type: 'text',
674
- text: `Note: "${articleUrl}" is a documentation category, not an article. Showing its menu contents below. For category URLs, use BrowseWixRESTDocsMenu directly next time.\n\n${text}`
676
+ text: `Note: "${articleUrl}" is not an article. Showing the closest documentation menu below; read the article you need from its links. For category URLs, use BrowseWixRESTDocsMenu directly next time.\n\n${text}`
675
677
  }
676
678
  ]
677
679
  };
@@ -746,7 +748,7 @@ export const addDocsTools = (server, allowedTools = [
746
748
  content: [
747
749
  {
748
750
  type: 'text',
749
- text: `Note: "${articleUrl}" is a documentation category, not an article. Showing its menu contents below. For category URLs, use BrowseWixRESTDocsMenu directly next time.\n\n${text}`
751
+ text: `Note: "${articleUrl}" is not an article. Showing the closest documentation menu below; read the article you need from its links. For category URLs, use BrowseWixRESTDocsMenu directly next time.\n\n${text}`
750
752
  }
751
753
  ]
752
754
  };
@@ -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
- // Fetch the skills index from the portal. In PR mode, pass repo+commitHash
173
- // to get the PR-aware index (includes recipes added via YAML in the PR).
174
- const skillsContentPromise = getSkillsByPortal(httpClient, {
175
- portalId: REST_PORTAL_ID,
176
- ...(repo && { repo }),
177
- ...(commitHash && { commitHash }),
178
- ...(skillsTags && skillsTags.length > 0 && { tags: skillsTags })
179
- }).catch((err) => {
180
- console.error('[WixREADME] Failed to fetch skills by portal:', err);
181
- return null;
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('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: send the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload them and get a live hosted site.');
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('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: send the user to https://www.wix.com/headless/drop?utm_campaign=mcp to upload them and get a live hosted site.');
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
@@ -6,7 +6,11 @@ import { wixMcpRequestSrc39Evid1607, executeWixApiToolRequestSrc39Evid1612 } fro
6
6
  * and concatenates text blocks with newlines.
7
7
  */
8
8
  export declare function extractText(result: WixToolCallResult): string;
9
- export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>): string | undefined;
9
+ export interface ToolResultClient {
10
+ name?: string;
11
+ userAgent?: string;
12
+ }
13
+ export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>, client?: ToolResultClient): string | undefined;
10
14
  export declare function createToolBiEvent(params: Parameters<typeof wixMcpRequestSrc39Evid1607>[0]): import("@wix/bi-logger-api-infra-data/v2/types").LogObject<import("@wix/bi-logger-api-infra-data/v2/types").wixMcpRequestSrc39Evid1607Params>;
11
15
  export declare function createExecuteWixAPICallToolBiEvent(params: Parameters<typeof executeWixApiToolRequestSrc39Evid1612>[0]): import("@wix/bi-logger-api-infra-data/v2/types").LogObject<import("@wix/bi-logger-api-infra-data/v2/types").executeWixApiToolRequestSrc39Evid1612Params>;
12
16
  //# sourceMappingURL=tool-bi.d.ts.map
@@ -11,7 +11,27 @@ export function extractText(result) {
11
11
  .join('\n');
12
12
  }
13
13
  const TOOL_RESULT_PREVIEW_MAX_CHARS = 4000;
14
- export function formatToolResultAdditionalFields(result, extras) {
14
+ // Codex keeps 10,000 tokens of a tool result by default, estimated at 4 bytes
15
+ // per token, and drops the middle of anything longer.
16
+ const CODEX_OUTPUT_BUDGET_BYTES = 40_000;
17
+ // Predicted from the default budget: a user who raised Codex's
18
+ // tool_output_token_limit is flagged here but not actually truncated.
19
+ function getCodexTruncation(resultText, client) {
20
+ const isCodex = [client?.name, client?.userAgent].some((identity) => identity?.toLowerCase().includes('codex'));
21
+ if (!isCodex) {
22
+ return undefined;
23
+ }
24
+ const resultBytes = new TextEncoder().encode(resultText).length;
25
+ if (resultBytes <= CODEX_OUTPUT_BUDGET_BYTES) {
26
+ return undefined;
27
+ }
28
+ return {
29
+ resultBytes,
30
+ budgetBytes: CODEX_OUTPUT_BUDGET_BYTES,
31
+ truncatedBytes: resultBytes - CODEX_OUTPUT_BUDGET_BYTES
32
+ };
33
+ }
34
+ export function formatToolResultAdditionalFields(result, extras, client) {
15
35
  const resultText = extractText(result);
16
36
  const contentTypes = [
17
37
  ...new Set(result.content?.map((content) => content.type) ?? [])
@@ -24,6 +44,7 @@ export function formatToolResultAdditionalFields(result, extras) {
24
44
  resultTextLength: resultText.length,
25
45
  resultPreviewTruncated: resultText.length > TOOL_RESULT_PREVIEW_MAX_CHARS,
26
46
  resultContentTypes: contentTypes,
47
+ codexTruncation: getCodexTruncation(resultText, client),
27
48
  ...extras
28
49
  });
29
50
  }
@@ -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${REST_METHOD_CONTRACT_RETRY_HINT}`;
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'];
@@ -175,7 +175,11 @@ export type CodeModeFetch = (url: string, init: RequestInit) => Promise<Response
175
175
  * and concatenates text blocks with newlines.
176
176
  */
177
177
  export declare function extractText(result: WixToolCallResult): string;
178
- export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>): string | undefined;
178
+ export interface ToolResultClient {
179
+ name?: string;
180
+ userAgent?: string;
181
+ }
182
+ export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>, client?: ToolResultClient): string | undefined;
179
183
  /** A single tool extracted from @wix/mcp, ready to execute. */
180
184
  export interface WixTool {
181
185
  /** Tool name (e.g. 'CallWixSiteAPI', 'SearchWixRESTDocumentation') */
@@ -175,7 +175,11 @@ export type CodeModeFetch = (url: string, init: RequestInit) => Promise<Response
175
175
  * and concatenates text blocks with newlines.
176
176
  */
177
177
  export declare function extractText(result: WixToolCallResult): string;
178
- export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>): string | undefined;
178
+ export interface ToolResultClient {
179
+ name?: string;
180
+ userAgent?: string;
181
+ }
182
+ export declare function formatToolResultAdditionalFields(result: WixToolCallResult, extras?: Record<string, unknown>, client?: ToolResultClient): string | undefined;
179
183
  /** A single tool extracted from @wix/mcp, ready to execute. */
180
184
  export interface WixTool {
181
185
  /** Tool name (e.g. 'CallWixSiteAPI', 'SearchWixRESTDocumentation') */