@wix/mcp 1.0.95 → 1.0.96

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.
@@ -26,7 +26,8 @@ function directRouteLines(includeReadme, includeImageUploadGuidance) {
26
26
  return [
27
27
  ...(includeReadme
28
28
  ? [
29
- `- **Creating a new site.** Skip the docs too. A site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}). Call one right away, without asking the user anything first: StartHeadlessBuild when the site needs Wix business features, otherwise StartDeployWebsite. WixSiteBuilder or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. Never create a site through ManageWixSite, CallWixSiteAPI or ExecuteWixAPI.`
29
+ `- **Creating a new site.** Skip the docs too. A site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}). Call one right away, without asking the user anything first: StartHeadlessBuild when the site needs Wix business features, otherwise StartDeployWebsite. WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. Never create a site through ManageWixSite, CallWixSiteAPI or ExecuteWixAPI.`,
30
+ '- **Suggesting domain names**: call GetSuggestedDomains directly, with the business or site name as the query.'
30
31
  ]
31
32
  : []),
32
33
  '- **Listing, showing or finding their Wix sites**: call ListWixSites directly.',
@@ -87,16 +88,17 @@ export const HELP_CENTER_IS_FOR_PEOPLE = "The Help Center is written for people
87
88
  * Each tool named here carries a comment pointing back to this text. A change to any of them (a
88
89
  * name, what it is for, a new site-creation tool) updates this text and that list with it.
89
90
  */
90
- export const SERVER_INSTRUCTIONS = `Wix: build and manage Wix sites. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (forms, a store, bookings, a CMS, members, payments). WixSiteBuilder or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. For anything else, except ListWixSites and UploadImageToWixSite, call WixREADME first.`;
91
+ export const SERVER_INSTRUCTIONS = `Wix: build and manage Wix sites. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide; StartHeadlessBuild returns the headless guide, for a site that needs Wix business features (forms, a store, bookings, a CMS, members, payments). WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor. Call WixREADME first, except ListWixSites, UploadImageToWixSite, GetSuggestedDomains.`;
91
92
  /** The tools SERVER_INSTRUCTIONS names. */
92
93
  export const SERVER_INSTRUCTIONS_TOOLS = [
93
94
  'StartDeployWebsite',
94
95
  'StartHeadlessBuild',
95
- 'WixSiteBuilder',
96
+ 'WixHarmonyEditorBuild',
96
97
  'SearchSiteTemplates',
97
98
  'WixREADME',
98
99
  'ListWixSites',
99
- 'UploadImageToWixSite'
100
+ 'UploadImageToWixSite',
101
+ 'GetSuggestedDomains'
100
102
  ];
101
103
  export const SITE_CREATION_COPY = {
102
104
  templatePick: {
@@ -115,7 +117,7 @@ export const toWidget = (text) => Object.values(SITE_CREATION_COPY).reduce((acc,
115
117
  */
116
118
  // The data the Wix REST API covers, product by product (from the ax-skills capability map), named
117
119
  // in the discovery tools' descriptions so a client's tool search finds them by what the user asks about.
118
- const WIX_API_DATA = "products, inventory, carts, checkouts, orders, bookings, services, staff, time slots, rentals, restaurant menus, online orders, reservations, events, tickets, RSVPs, blog posts, CMS collections and items, pricing plans, subscriptions, portfolios, gift cards, coupons, donations, forms, form submissions, contacts, members, loyalty points, groups, reviews, inbox messages, CRM tasks, automations, email campaigns, payments, invoices, SEO, redirects, translations, locations, analytics, media files, site properties, installed apps, and the account's sites and domains";
120
+ const WIX_API_DATA = "products, inventory, carts, checkouts, orders, bookings, services, staff, time slots, rentals, restaurant menus, online orders, reservations, events, tickets, RSVPs, blog posts, CMS collections and items, pricing plans, subscriptions, portfolios, gift cards, coupons, donations, forms, form submissions, contacts, members, loyalty points, groups, reviews, inbox messages, CRM tasks, automations, email campaigns, payments, invoices, SEO, redirects, translations, locations, analytics, media files, site properties, installed apps, and the account's sites and domains (searching whether a domain is available, buying one, and connecting one bought elsewhere)";
119
121
  const EXECUTE_WIX_API_INTRO = dedent `
120
122
  Reads and changes the data of a Wix site or account: query, list, count, create, update and delete it with one JavaScript function over the Wix REST API.
121
123
  One function holds the whole task: chained calls, pagination, loops, branching, and the reads that feed its writes.
@@ -432,8 +434,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
432
434
  ${reminder}
433
435
  `,
434
436
  ManageWixSite: dedent `
435
- Use account level API in order to update a site.
436
- Account-level only: this tool acts on the account itself, or on sites as objects within it, and sends no site context. Anything that reads or writes inside a site is site-level and does not belong here — use a site-level tool with that site's \`siteId\`.
437
+ Calls an account-level Wix API: one that acts on the account itself (its sites list, domains, account settings) or on sites as objects in it (create, rename, duplicate, delete a site). It sends no site, so an API that reads or writes inside a site (its products, data, bookings, members, pages, publishing) fails here: call that through ExecuteWixAPI with the site's \`siteId\`.
437
438
  For POST/PATCH/PUT requests, pass the request body as a JSON object or array in the "body" parameter with all the required fields and values as described in the API schema, code examples, or docs you retrieved (e.g. body: {"name": "value", "nested": {"key": "value"}} or body: [{"key": "value"}]).
438
439
  The API endpoint url param MUST ALWAYS be taken from the conversation context.
439
440
  By conversation context we mean the endpoint url was given in the user prompt or got into the conversation context ${includeReadme ? 'by the "WixREADME" tool or ' : ''}by the "SearchWixRESTDocumentation" tool or by the "BrowseWixRESTDocsMenu" tool or by the "ReadFullDocsArticle" tool.
@@ -503,7 +504,7 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
503
504
  ${reminder}
504
505
  `,
505
506
  WixSiteCreation: dedent `
506
- A new Wix site is written in code and uploaded: call StartDeployWebsite for the upload guide, or StartHeadlessBuild when the site needs Wix business features. WixSiteBuilder or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.
507
+ Returns which Wix tool creates a new site, and creates nothing itself. A new site is written in code and uploaded: StartDeployWebsite returns the upload guide, and StartHeadlessBuild returns the guide for a site that needs Wix business features. Use WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for Wix's editor, a template, Wix Studio or the Classic Editor.
507
508
  `,
508
509
  'import-claude-design-from-url': dedent `
509
510
  Import a design created in Claude's design tool into Wix. Only for the signed \`claudeusercontent.com\` bundle URL Claude Design's "Send to Wix" produces (a self-contained HTML bundle with all images, fonts, and styles inlined). Creates a live Wix-hosted site and returns its URL.
@@ -945,16 +946,16 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
945
946
  `,
946
947
  ExecuteWixAPI: executeWixApiDescription,
947
948
  // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
948
- WixSiteBuilder: dedent `
949
- A secondary site-creation tool, only for a user who asked for the Wix Harmony Editor or for Wix to design the site. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
949
+ WixHarmonyEditorBuild: dedent `
950
+ A secondary site-creation tool, only for a user who asked for the Wix Harmony Editor or for Wix to design the site in Wix's editor. A request for a site on Wix, or for Wix to build or make one, is neither. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
950
951
  Builds a site with the Wix Harmony Editor: Wix designs a first version from the user's description, and the user keeps designing it in Wix's editor. Shows the build's progress and a preview.
951
- Call it when the user asked for the Wix Harmony Editor, or for Wix to design the site, in their own words or as their answer to your question. ${EDITOR_TAIL}
952
+ Call it when the user asked for the Wix Harmony Editor, or for Wix to design the site in Wix's editor, in their own words or as their answer to your question, and quote those words in \`editorRequest\`. ${EDITOR_TAIL}
952
953
  `,
953
954
  // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
954
955
  SearchSiteTemplates: dedent `
955
956
  A secondary site-creation tool, only for a user who asked for a template, Wix Studio or the Classic Editor. Don't call it for any other site request, not even to check with the flag false. A new site is otherwise written in code and uploaded, with StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features.
956
957
  Shows a gallery of Wix templates, from Harmony, Wix Studio or the Classic Editor, for a site the user will design in Wix's editor. Wix templates come with Wix's business features (a store, online ordering, bookings, forms), so a request for a template comes here even when the site needs them.
957
- Call it when the user asked for a template, Wix Studio or the Classic Editor, in their own words or as their answer to your question. ${EDITOR_TAIL} ${SITE_CREATION_COPY.templatePick.nonWidget}
958
+ Call it when the user asked for a template, Wix Studio or the Classic Editor, in their own words or as their answer to your question, and quote those words in \`templateRequest\`. ${EDITOR_TAIL} ${SITE_CREATION_COPY.templatePick.nonWidget}
958
959
  `,
959
960
  CreateSiteFromTemplate: dedent `
960
961
  Creates a Wix site from a specific template (by \`metaSiteId\` or \`templateId\`), publishes it when possible, and shows a live preview with links to the editor and the published site.
@@ -991,20 +992,20 @@ export function buildToolDescriptions(includeReadme, includeImageUploadGuidance
991
992
  UploadHeadlessWebsiteFiles: dedent `
992
993
  The upload step of the StartDeployWebsite and StartHeadlessBuild guides, and nothing else: puts a finished website's files — a static site (index.html plus its CSS/JS/images, a static build, or a zip of the site) or a Wix Astro build — onto an existing Wix Headless site by its metaSiteId. Call it ONLY after one of them has returned its guide in this conversation and the site exists: for a new site, first run the recipe's provision call and pass the metaSiteId it returns; for a site deployed earlier, pass its metaSiteId. It does not create sites. Every upload replaces the whole file set and the URL stays. Returns the live URL and the dashboard link, and points back to the recipe for what comes next.
993
994
  Pass the files themselves: in ${FILE_PARAM_CLIENTS} as \`attachments\` (any type, a zip is unpacked), elsewhere as the \`files\` text bundle (\`=== FILE: <path> ===\` sections, \`base64\` tag for small binaries). A static site must contain index.html at its root. A Wix Astro build with no file handle, from a shell that reaches the internet, goes in two calls: zip the build, call with \`prepare: true\` and the zip's \`size\` in bytes, PUT the zips it names with the \`curl\` commands it returns (the build, and the project source when the site's repository accepts it), then call again with the \`uploadKey\` it returned; that releases the build. The two calls, literally: {siteId, prepare: true, size} then {siteId, uploadKey, sourceUploadId?, expectedHeadRevisionId?, releaseNotes?}. A sandbox whose network is limited to a few hosts cannot upload to those URLs. A static site does not use the two calls. A Wix Astro build (the project's .wix/ and dist/ after \`astro build\` with the Wix hosting adapter, zipped from the project root) needs no index.html: it is released to the site's Wix hosting instead, server-rendered pages, the same site and URL. The project's source is stored with the release when the upload holds the project and the site's repository accepts it.
994
- NOT a site-creation tool: a request to create, build, design or generate a site — with or without a description — never comes here first. Unless the user asked for Wix's editor or a template (WixSiteBuilder, SearchSiteTemplates), call StartDeployWebsite right away, without asking the user anything: it also takes files the user brought, uploaded first even when the site needs business features. StartHeadlessBuild instead for a new site with no files that needs Wix business features. Also NOT for media uploads to an existing site (UploadImageToWixSite) or any other API call (ExecuteWixAPI).
995
+ NOT a site-creation tool: a request to create, build, design or generate a site — with or without a description — never comes here first. Unless the user asked for Wix's editor or a template (WixHarmonyEditorBuild, SearchSiteTemplates), call StartDeployWebsite right away, without asking the user anything: it also takes files the user brought, uploaded first even when the site needs business features. StartHeadlessBuild instead for a new site with no files that needs Wix business features. Also NOT for media uploads to an existing site (UploadImageToWixSite) or any other API call (ExecuteWixAPI).
995
996
  `,
996
997
  // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
997
998
  StartDeployWebsite: dedent `
998
999
  Creates a website on Wix and puts it live, or replaces the files of one deployed earlier. You write the site's code, or take the files the user brought (HTML/CSS/JS, a static build, a zip, another builder's export), and this returns the guide to create the site and upload them. Updates keep the same site and URL. Works with or without a Wix login (anonymous deploy with a save link), through the Wix MCP alone or from a shell. Call it right away, without asking the user anything first.
999
1000
  When a new site needs Wix business features (${WIX_BUSINESS_FEATURES}), call StartHeadlessBuild instead. When the user brought files and the site needs those features, upload the files here first; the upload's result then points to StartHeadlessBuild to connect them.
1000
1001
  Returns the guide; follow it and do the work yourself. Upload with UploadHeadlessWebsiteFiles once the site exists; the drop page is only the fallback.
1001
- NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (WixSiteBuilder, SearchSiteTemplates); a Claude Design bundle URL (import-claude-design-from-url); migrating a live site by URL or CSV exports; adding HTML or code into an existing Wix editor site; uploading images or documents to a site's media (UploadImageToWixSite).
1002
+ NOT for: a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design bundle URL (import-claude-design-from-url); migrating a live site by URL or CSV exports; adding HTML or code into an existing Wix editor site; uploading images or documents to a site's media (UploadImageToWixSite).
1002
1003
  `,
1003
1004
  // Named in SERVER_INSTRUCTIONS: when this tool's name or what it is for changes, update them to match.
1004
1005
  StartHeadlessBuild: dedent `
1005
1006
  Returns Wix's headless guide, for a site that needs Wix business features (${WIX_BUSINESS_FEATURES}), run by Wix as a Wix Headless site. You write the site; the guide creates it, connects the features, and uploads it. Also to add those features to an existing Wix Headless site (one deployed from files is one), from a project download link, or to move a Wix site to code.
1006
1007
  Call it right away: it only returns the guide, and the site's details are not needed first.
1007
- NOT for: a site that needs no Wix business feature, or files the user brought that are not live yet (StartDeployWebsite: upload them first, then come back here for the features); a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor, even when the site needs business features (WixSiteBuilder, SearchSiteTemplates); a Claude Design URL (import-claude-design-from-url).
1008
+ NOT for: a site that needs no Wix business feature, or files the user brought that are not live yet (StartDeployWebsite: upload them first, then come back here for the features); a user who asked for Wix's editor, the Wix Harmony Editor, a template, Wix Studio or the Classic Editor, even when the site needs business features (WixHarmonyEditorBuild, SearchSiteTemplates); a Claude Design URL (import-claude-design-from-url).
1008
1009
  `
1009
1010
  };
1010
1011
  // The tool's former name, kept for importers that read it by that key.
@@ -1275,7 +1276,7 @@ export function withUsageGuideOnEveryTool(config) {
1275
1276
  */
1276
1277
  export const widgetSiteCreationConfig = {
1277
1278
  toolDescriptions: Object.fromEntries([
1278
- 'WixSiteBuilder',
1279
+ 'WixHarmonyEditorBuild',
1279
1280
  'WixSiteCreation',
1280
1281
  'SearchSiteTemplates',
1281
1282
  'CreateSiteFromTemplate'
@@ -5,6 +5,7 @@ import { captureException } from '@sentry/node';
5
5
  import { fetchArticleContent, fetchSourceContent, isSkillsRegistryUrl, parseSourceContentUrl } from './fetch-article.js';
6
6
  import { normalizeMenuContent, summarizeLargeMenu, fetchMenuContent, MENU_CONTENT_THRESHOLD } from './normalize-menu-content.js';
7
7
  import { fetchPlatformizedMenu, MenuNotFoundError } from './browse-menu.js';
8
+ import { closestMenuLinks, closestSkillFile, parentMenuUrls, skillManifestUrl, subMenusToRead } from './guessed-url.js';
8
9
  import { isQueryOrSearchEndpoint, QUERY_CAPABILITY_MARKER } from './searchFormatting.js';
9
10
  import { addGetToKnowTools, addGetSiteContextTool } from './get-to-know.js';
10
11
  import { addStartHeadlessBuildTool } from './start-headless-build.js';
@@ -55,7 +56,17 @@ function shouldTryMenuFallbackForDocsRead(error, articleUrl) {
55
56
  if (!message.includes('404') || !isMenuFallbackUrl) {
56
57
  return false;
57
58
  }
58
- return !['This resource is deprecated', 'No schema found'].some((knownArticleError) => message.includes(knownArticleError));
59
+ return !isKnownArticleError(error);
60
+ }
61
+ // A 404 that says why (a deprecated page, a method with no schema) is the answer itself.
62
+ function isKnownArticleError(error) {
63
+ const message = getErrorMessage(error);
64
+ return [
65
+ 'This resource is deprecated',
66
+ 'This method is deprecated',
67
+ 'This article is deprecated',
68
+ 'No schema found'
69
+ ].some((knownArticleError) => message.includes(knownArticleError));
59
70
  }
60
71
  function formatDocsReadError(toolAction, error) {
61
72
  return `Could not fetch ${toolAction}: "${getErrorMessage(error)}".`;
@@ -139,6 +150,51 @@ export const addDocsTools = (server, allowedTools = [
139
150
  ? summarizeLargeMenu(menuContent, url)
140
151
  : normalizeMenuContent(menuContent);
141
152
  };
153
+ // A docs or skills page that 404'd, most often a URL rewritten or guessed from a real one:
154
+ // the closest file in the skill or its folder's files, or the nearest menu above a docs page
155
+ // with its closest entries (one level into its closest sub-menus when none of its own is
156
+ // close). Null when nothing near exists, so the 404 stands.
157
+ const nearestToMissingPage = async (httpClient, url, isSkillsPage) => {
158
+ try {
159
+ if (isSkillsPage) {
160
+ const manifestUrl = skillManifestUrl(url);
161
+ if (!manifestUrl)
162
+ return null;
163
+ const manifest = JSON.parse(await fetchArticleContent(httpClient, manifestUrl, 'article'));
164
+ const guess = closestSkillFile(url, manifest, manifestUrl);
165
+ if (!guess)
166
+ return null;
167
+ if (guess.kind === 'file') {
168
+ const text = await fetchArticleContent(httpClient, guess.url, 'article');
169
+ return `Note: "${url}" does not exist. Found a similar file in the skill, ${guess.url}, and read it:\n\n${text}`;
170
+ }
171
+ return `Note: "${url}" does not exist, and no file in ${guess.folder ? `its folder (${guess.folder}/)` : 'the skill'} has a name close to it. ${guess.folder ? 'The files in that folder' : "The skill's top-level folders and files"}, to read the one you need (every file: ${guess.manifestUrl}):\n\n${guess.files.map((f) => `- ${f}`).join('\n')}`;
172
+ }
173
+ for (const menuUrl of parentMenuUrls(url)) {
174
+ const text = await fetchMenuTextForUrl(httpClient, menuUrl);
175
+ if (!text)
176
+ continue;
177
+ const subMenus = (await Promise.all(subMenusToRead(text, menuUrl, url).map(async (sub) => ({
178
+ sub,
179
+ text: await fetchMenuTextForUrl(httpClient, sub).catch(() => null)
180
+ })))).filter((m) => !!m.text);
181
+ const closest = closestMenuLinks([text, ...subMenus.map((m) => m.text)].join('\n'), menuUrl, url);
182
+ return [
183
+ `Note: "${url}" does not exist. Showing the nearest documentation menu above it, ${menuUrl}; read the page you need from its links.`,
184
+ ...(closest.length
185
+ ? [
186
+ `Closest to what you asked for:\n${closest.map((l) => `- ${l}`).join('\n')}`
187
+ ]
188
+ : []),
189
+ text
190
+ ].join('\n\n');
191
+ }
192
+ }
193
+ catch (error) {
194
+ logger.error(`[docs] no nearest page for ${url}: ${error}`);
195
+ }
196
+ return null;
197
+ };
142
198
  // The two site-creation guides register first, so they head the tool list: an agent that
143
199
  // loads every definition at once and gets a cut-down copy keeps the start and the end.
144
200
  // On by default: they are routing slots, not docs sources.
@@ -865,6 +921,13 @@ export const addDocsTools = (server, allowedTools = [
865
921
  // menu fallback failed too — fall through to error below
866
922
  }
867
923
  }
924
+ if (getErrorMessage(error).includes('404') &&
925
+ !isKnownArticleError(error)) {
926
+ const nearest = await nearestToMissingPage(httpClient, articleUrl, isSkillsPage);
927
+ if (nearest) {
928
+ return { content: [{ type: 'text', text: nearest }] };
929
+ }
930
+ }
868
931
  return {
869
932
  isError: true,
870
933
  content: [
@@ -0,0 +1,41 @@
1
+ export declare const slugWords: (slug: string) => Set<string>;
2
+ /** Shared words over all words (Jaccard): 1 for the same words, 0 for none in common. */
3
+ export declare function slugSimilarity(a: string, b: string): number;
4
+ /** A file at least this close to the asked name, and the only one that close, is read in its place. */
5
+ export declare const SKILL_FILE_MATCH = 0.5;
6
+ export type SkillFileGuess = {
7
+ kind: 'file';
8
+ url: string;
9
+ path: string;
10
+ } | {
11
+ kind: 'folder';
12
+ folder: string;
13
+ files: string[];
14
+ manifestUrl: string;
15
+ };
16
+ /**
17
+ * The closest file to a skills registry URL that 404'd, from the skill's manifest
18
+ * (`https://www.wix.com/skills/<skill>.manifest.json`, `{ base, files: [{ path }] }`).
19
+ * A file of the same name in another folder counts when it is the only one; a near name
20
+ * counts in the same folder only. Null when the URL is not a skill file or the manifest
21
+ * can't be read.
22
+ */
23
+ export declare function closestSkillFile(url: string, manifest: {
24
+ base?: string;
25
+ files?: {
26
+ path: string;
27
+ }[];
28
+ }, manifestUrl: string): SkillFileGuess | null;
29
+ /** The manifest of the skill a registry URL is in, or null for a URL that names no skill. */
30
+ export declare function skillManifestUrl(url: string): string | null;
31
+ /** How far above a docs page the nearest menu is looked for. */
32
+ export declare const MENU_WALK_UP_LEVELS = 3;
33
+ /** The menus above a dev.wix.com docs URL, nearest first. */
34
+ export declare function parentMenuUrls(url: string): string[];
35
+ /** The menu's own entries (links under it) whose last segment is closest to the asked page, best first. */
36
+ export declare function closestMenuLinks(menuText: string, menuUrl: string, askedUrl: string, limit?: number): string[];
37
+ /** Below this, a menu's best entry is no answer, and its closest sub-menus are read too. */
38
+ export declare const MENU_ENTRY_MATCH = 0.75;
39
+ /** The menu's sub-menus worth reading for the asked page: its entries sharing a word with it, closest first. */
40
+ export declare function subMenusToRead(menuText: string, menuUrl: string, askedUrl: string, limit?: number): string[];
41
+ //# sourceMappingURL=guessed-url.d.ts.map
@@ -0,0 +1,185 @@
1
+ /**
2
+ * What ReadFullDocsArticle offers for a docs or skills URL that 404s. Most of these are
3
+ * guessed: a method slug invented under a real resource, a recipe link "tidied" into a
4
+ * path that never existed, a skill file renamed since an agent's notes were written.
5
+ * Instead of a bare not-found, the agent gets the nearest thing that exists:
6
+ *
7
+ * - a skills registry file: the skill's manifest names every file, so a file whose name
8
+ * is close to the asked one, in the same folder, is read in its place; otherwise the
9
+ * folder's files are listed;
10
+ * - a dev.wix.com docs page: the nearest menu above it, with the links whose names are
11
+ * closest to the asked page listed first.
12
+ */
13
+ import { normalizeSkillsUrl } from './fetch-article.js';
14
+ const SKILLS_BASE = 'https://www.wix.com/skills/';
15
+ // Every host and path ReadFullDocsArticle accepts for the skills registry, as the
16
+ // www.wix.com/skills/ URL whose manifest is next to it.
17
+ const registryUrl = (url) => normalizeSkillsUrl(url).replace(/^https:\/\/(?:dev\.wix\.com(?:\/docs)?|www\.wix\.com\/velo\/reference)\/skills\//, SKILLS_BASE);
18
+ const DOCS_BASE = 'https://dev.wix.com/docs/';
19
+ // Words that say nothing about which file or page is meant.
20
+ const FILLER = new Set([
21
+ '',
22
+ 'md',
23
+ 'a',
24
+ 'an',
25
+ 'the',
26
+ 'and',
27
+ 'or',
28
+ 'of',
29
+ 'to',
30
+ 'wix',
31
+ 's'
32
+ ]);
33
+ export const slugWords = (slug) => new Set(slug
34
+ .toLowerCase()
35
+ .replace(/\.md$/, '')
36
+ .split(/[^a-z0-9]+/)
37
+ .filter((w) => !FILLER.has(w))
38
+ // A plural names the same thing: posts, post.
39
+ .map((w) => (w.length > 3 && w.endsWith('s') ? w.slice(0, -1) : w)));
40
+ /** Shared words over all words (Jaccard): 1 for the same words, 0 for none in common. */
41
+ export function slugSimilarity(a, b) {
42
+ const x = slugWords(a);
43
+ const y = slugWords(b);
44
+ if (x.size === 0 || y.size === 0)
45
+ return 0;
46
+ let shared = 0;
47
+ for (const w of x)
48
+ if (y.has(w))
49
+ shared++;
50
+ return shared / (x.size + y.size - shared);
51
+ }
52
+ const lastSegment = (path) => path.slice(path.lastIndexOf('/') + 1);
53
+ const folderOf = (path) => path.includes('/') ? path.slice(0, path.lastIndexOf('/')) : '';
54
+ /** A file at least this close to the asked name, and the only one that close, is read in its place. */
55
+ export const SKILL_FILE_MATCH = 0.5;
56
+ /**
57
+ * The closest file to a skills registry URL that 404'd, from the skill's manifest
58
+ * (`https://www.wix.com/skills/<skill>.manifest.json`, `{ base, files: [{ path }] }`).
59
+ * A file of the same name in another folder counts when it is the only one; a near name
60
+ * counts in the same folder only. Null when the URL is not a skill file or the manifest
61
+ * can't be read.
62
+ */
63
+ export function closestSkillFile(url, manifest, manifestUrl) {
64
+ const normalized = registryUrl(url).replace(/[?#].*$/, '');
65
+ if (!normalized.startsWith(SKILLS_BASE))
66
+ return null;
67
+ const [, ...rest] = normalized.slice(SKILLS_BASE.length).split('/');
68
+ const asked = rest.join('/');
69
+ const paths = (manifest.files ?? []).map((f) => f.path);
70
+ if (!asked || paths.length === 0)
71
+ return null;
72
+ const base = manifest.base ?? manifestUrl.replace(/\.manifest\.json$/, '/');
73
+ const name = lastSegment(asked);
74
+ const folder = folderOf(asked);
75
+ const sameName = paths.filter((p) => lastSegment(p) === name);
76
+ if (sameName.length === 1) {
77
+ return { kind: 'file', path: sameName[0], url: base + sameName[0] };
78
+ }
79
+ const ranked = paths
80
+ .filter((p) => folderOf(p) === folder)
81
+ .map((p) => ({ p, score: slugSimilarity(lastSegment(p), name) }))
82
+ .sort((a, b) => b.score - a.score);
83
+ const [best, next] = ranked;
84
+ if (best &&
85
+ best.score >= SKILL_FILE_MATCH &&
86
+ best.score > (next?.score ?? 0)) {
87
+ return { kind: 'file', path: best.p, url: base + best.p };
88
+ }
89
+ if (ranked.length > 0) {
90
+ return {
91
+ kind: 'folder',
92
+ folder,
93
+ files: ranked.map((r) => base + r.p),
94
+ manifestUrl
95
+ };
96
+ }
97
+ // The folder doesn't exist either: the skill's top-level folders and files.
98
+ const top = [...new Set(paths.map((p) => p.split('/')[0]))].sort();
99
+ return {
100
+ kind: 'folder',
101
+ folder: '',
102
+ files: top.map((t) => base + t),
103
+ manifestUrl
104
+ };
105
+ }
106
+ /** The manifest of the skill a registry URL is in, or null for a URL that names no skill. */
107
+ export function skillManifestUrl(url) {
108
+ const normalized = registryUrl(url);
109
+ if (!normalized.startsWith(SKILLS_BASE))
110
+ return null;
111
+ const skill = normalized.slice(SKILLS_BASE.length).split(/[/?#]/)[0];
112
+ return skill ? `${SKILLS_BASE}${skill}.manifest.json` : null;
113
+ }
114
+ /** How far above a docs page the nearest menu is looked for. */
115
+ export const MENU_WALK_UP_LEVELS = 3;
116
+ // A menu this close to the root (`/docs/velo`, `/docs/api-reference/crm`) lists whole
117
+ // products, which says nothing about the page that was asked for.
118
+ const MIN_MENU_SEGMENTS = 3;
119
+ /** The menus above a dev.wix.com docs URL, nearest first. */
120
+ export function parentMenuUrls(url) {
121
+ const clean = url.replace(/[?#].*$/, '').replace(/\/+$/, '');
122
+ if (!clean.startsWith(DOCS_BASE))
123
+ return [];
124
+ const segments = clean.slice(DOCS_BASE.length).split('/');
125
+ const out = [];
126
+ for (let up = 1; up <= MENU_WALK_UP_LEVELS; up++) {
127
+ const kept = segments.length - up;
128
+ if (kept < MIN_MENU_SEGMENTS)
129
+ break;
130
+ out.push(DOCS_BASE + segments.slice(0, kept).join('/'));
131
+ }
132
+ return out;
133
+ }
134
+ /** The menu's own entries (links under it) whose last segment is closest to the asked page, best first. */
135
+ export function closestMenuLinks(menuText, menuUrl, askedUrl, limit = 3) {
136
+ const asked = lastSegment(askedUrl.replace(/[?#].*$/, '').replace(/\/+$/, ''));
137
+ const under = `${menuUrl.replace(/\/+$/, '')}/`;
138
+ const links = [
139
+ ...new Set(menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? [])
140
+ ].filter((l) => l.startsWith(under));
141
+ return links
142
+ .map((l) => ({
143
+ l,
144
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, '')), asked)
145
+ }))
146
+ .filter((r) => r.score > 0)
147
+ .sort((a, b) => b.score - a.score)
148
+ .slice(0, limit)
149
+ .map((r) => r.l);
150
+ }
151
+ const SMALL_MENU = 3;
152
+ /** Below this, a menu's best entry is no answer, and its closest sub-menus are read too. */
153
+ export const MENU_ENTRY_MATCH = 0.75;
154
+ /** The menu's sub-menus worth reading for the asked page: its entries sharing a word with it, closest first. */
155
+ export function subMenusToRead(menuText, menuUrl, askedUrl, limit = 2) {
156
+ const asked = lastSegment(askedUrl.replace(/[?#].*$/, '').replace(/\/+$/, ''));
157
+ const under = `${menuUrl.replace(/\/+$/, '')}/`;
158
+ // A method entry (`- [Create Order](…) _(POST)_`) is a page, not a menu.
159
+ const methods = new Set([
160
+ ...menuText.matchAll(/\]\((https:\/\/dev\.wix\.com\/docs\/[^)\s]+)\)\s*_\((?:GET|POST|PUT|PATCH|DELETE)\)_/g)
161
+ ].map((m) => m[1]));
162
+ // Direct children only: the legacy menu text also lists pages nested further down.
163
+ const entries = [
164
+ ...new Set(menuText.match(/https:\/\/dev\.wix\.com\/docs\/[^\s)\]>"'`]+/g) ?? [])
165
+ ].filter((l) => l.startsWith(under) &&
166
+ !l.slice(under.length).replace(/\.md$/, '').includes('/') &&
167
+ !methods.has(l));
168
+ const scored = entries
169
+ .map((l) => ({
170
+ l,
171
+ score: slugSimilarity(lastSegment(l.replace(/\.md$/, '')), asked)
172
+ }))
173
+ .sort((a, b) => b.score - a.score);
174
+ if (scored.some((s) => s.score >= MENU_ENTRY_MATCH))
175
+ return [];
176
+ // A menu of a few entries is read whole: none may share a word (catalog-v1 holds
177
+ // catalog and inventory; products sit under catalog).
178
+ if (entries.length <= SMALL_MENU)
179
+ return entries;
180
+ return scored
181
+ .filter((s) => s.score > 0)
182
+ .slice(0, limit)
183
+ .map((s) => s.l);
184
+ }
185
+ //# sourceMappingURL=guessed-url.js.map
@@ -1,4 +1,4 @@
1
- export declare const FREE_TEXT_INFO_PARAMS: readonly ["userRequest", "siteDescription", "siteForm", "siteFunctionality", "agentIdentity", "wixAccess", "reason", "whyNotExecuteWixAPI"];
1
+ export declare const FREE_TEXT_INFO_PARAMS: readonly ["userRequest", "siteDescription", "siteForm", "siteFunctionality", "agentIdentity", "wixAccess", "reason", "whyNotExecuteWixAPI", "editorRequest", "templateRequest"];
2
2
  export declare function redactPersonalData(text: string): string;
3
3
  export declare function serializeToolParamsForBi(args: unknown): string;
4
4
  //# sourceMappingURL=redact-personal-data.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { parseFileBundle } from '../api-call/site-files.js';
1
2
  // Free-text arguments that agents fill in for BI. Their descriptions ask for no
2
3
  // personal data; this is the backstop for what comes through anyway, applied to
3
4
  // these fields only before tool arguments are recorded. Other arguments (API
@@ -10,7 +11,9 @@ export const FREE_TEXT_INFO_PARAMS = [
10
11
  'agentIdentity',
11
12
  'wixAccess',
12
13
  'reason',
13
- 'whyNotExecuteWixAPI'
14
+ 'whyNotExecuteWixAPI',
15
+ 'editorRequest',
16
+ 'templateRequest'
14
17
  ];
15
18
  const EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
16
19
  // A secret's value after its name: "password: x", "token=x", "the password is x",
@@ -70,10 +73,76 @@ function redactInfoParams(value) {
70
73
  }
71
74
  return redacted;
72
75
  }
76
+ // A `files` argument carries a site's text whole: the upload tool's text bundle
77
+ // (`=== FILE: <path> ===` sections, `base64` for binaries) or a list of
78
+ // `{ path, content }` entries. The BI event goes out as a GET with the
79
+ // arguments in the query string, and a bundle past about 12 KB fails that
80
+ // request and loses the row (2026-10-28: every upload above that size was
81
+ // missing from BI). Recorded instead: the paths and their sizes, as the upload
82
+ // parses them (a base64 file's decoded size), with the path list cut at
83
+ // MAX_LISTED_PATHS_CHARS so a 2,000-file upload stays far below that limit too.
84
+ const MAX_LISTED_PATHS_CHARS = 2_000;
85
+ function summarizeFileBundle(bundle) {
86
+ let sections;
87
+ try {
88
+ sections = parseFileBundle(bundle).map((file) => ({
89
+ path: file.path,
90
+ bytes: file.bytes.length
91
+ }));
92
+ }
93
+ catch {
94
+ sections = [];
95
+ }
96
+ if (sections.length === 0) {
97
+ return `${new TextEncoder().encode(bundle).length} bytes, no file sections`;
98
+ }
99
+ return formatFileSummary(sections);
100
+ }
101
+ function formatFileSummary(files) {
102
+ const total = files.reduce((sum, file) => sum + file.bytes, 0);
103
+ let listed = '';
104
+ let shown = 0;
105
+ for (const file of files) {
106
+ const entry = `${shown ? ', ' : ''}${file.path} (${file.bytes})`;
107
+ if (listed.length + entry.length > MAX_LISTED_PATHS_CHARS)
108
+ break;
109
+ listed += entry;
110
+ shown++;
111
+ }
112
+ const more = files.length - shown;
113
+ return `${files.length} file(s), ${total} bytes: ${listed}${more ? `${shown ? ', ' : ''}and ${more} more` : ''}`;
114
+ }
115
+ function compactFileParams(value) {
116
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
117
+ return value;
118
+ }
119
+ const record = value;
120
+ const files = record.files;
121
+ if (typeof files === 'string') {
122
+ return { ...record, files: summarizeFileBundle(files) };
123
+ }
124
+ if (Array.isArray(files) &&
125
+ files.length > 0 &&
126
+ files.every((entry) => entry &&
127
+ typeof entry === 'object' &&
128
+ typeof entry.content === 'string')) {
129
+ return {
130
+ ...record,
131
+ files: formatFileSummary(files.map((entry) => ({
132
+ path: entry.path ?? '?',
133
+ bytes: new TextEncoder().encode(entry.content).length
134
+ })))
135
+ };
136
+ }
137
+ return value;
138
+ }
139
+ function prepareForBi(value) {
140
+ return compactFileParams(redactInfoParams(value));
141
+ }
73
142
  // Tool arguments as recorded in BI and error reports: the arguments object, or
74
143
  // the MCP SDK's positional argument list, with the free-text info fields
75
- // redacted.
144
+ // redacted and a `files` argument reduced to its paths and sizes.
76
145
  export function serializeToolParamsForBi(args) {
77
- return JSON.stringify(Array.isArray(args) ? args.map(redactInfoParams) : redactInfoParams(args));
146
+ return JSON.stringify(Array.isArray(args) ? args.map(prepareForBi) : prepareForBi(args));
78
147
  }
79
148
  //# sourceMappingURL=redact-personal-data.js.map
@@ -1,4 +1,4 @@
1
- export declare const SITE_BUILDER_TOOL_NAME = "WixSiteBuilder";
1
+ export declare const SITE_BUILDER_TOOL_NAME = "WixHarmonyEditorBuild";
2
2
  export declare const EDIT_SITE_TOOL_NAME = "EditSite";
3
3
  export declare const PULL_SITE_JOB_TOOL_NAME = "pullSiteCreationJob";
4
4
  export declare const CLAIM_SITE_TOOL_NAME = "ClaimAnonymousSite";
@@ -10,7 +10,7 @@ export declare const PICK_IMAGES_TOOL_NAME = "PickImagesForWixMedia";
10
10
  export declare const MEDIA_UPLOAD_URL_TOOL_NAME = "CreateWixMediaUploadUrl";
11
11
  /** The Wix business features that send a site to StartHeadlessBuild: what wix-headless-kit ships. */
12
12
  export declare const WIX_BUSINESS_FEATURES = "a store, bookings or appointments, rentals, events and tickets, a restaurant menu with online orders or reservations, a blog, a portfolio, pricing plans and memberships, donations, Wix Forms, members and login, a CMS, payments";
13
- /** WixSiteBuilder's and SearchSiteTemplates' answer when the agent says the user did not ask for them. */
13
+ /** WixHarmonyEditorBuild's and SearchSiteTemplates' answer when the agent says the user did not ask for them. */
14
14
  /**
15
15
  * The widget payload of a refused editor call: the gallery and builder widgets render nothing
16
16
  * for it, since the host mounts them for every call of their tool.
@@ -20,7 +20,7 @@ export declare const NOT_REQUESTED_WIDGET_PAYLOAD: {
20
20
  };
21
21
  export declare const EDITOR_NOT_REQUESTED_MESSAGE = "Nothing was created: the user did not ask for this. A new site is written in code and uploaded: call StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features. Don't offer Wix's editor or a template instead.";
22
22
  /** WixSiteCreation's answer: it no longer opens a chooser, it sends the agent to the code path. */
23
- export declare const SITE_CREATION_ROUTE_MESSAGE = "A new site is written in code and uploaded. Call StartDeployWebsite now for the upload guide, or StartHeadlessBuild when the site needs Wix business features. Use WixSiteBuilder or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.";
23
+ export declare const SITE_CREATION_ROUTE_MESSAGE = "A new site is written in code and uploaded. Call StartDeployWebsite now for the upload guide, or StartHeadlessBuild when the site needs Wix business features. Use WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.";
24
24
  /** Fallback actions when the industry is generic or unclear. */
25
25
  export declare const SITE_CREATION_DEFAULT_ACTIONS = "add products, set up bookings, and adjust your content";
26
26
  /**
@@ -1,7 +1,7 @@
1
1
  // Tool name constants - separated to avoid circular dependencies during widget builds
2
2
  // These constants can be imported by both the widgets and the tool implementations
3
3
  // Named in SERVER_INSTRUCTIONS (config/default-config.ts): when this tool's name or what it is for changes, update them to match.
4
- export const SITE_BUILDER_TOOL_NAME = 'WixSiteBuilder';
4
+ export const SITE_BUILDER_TOOL_NAME = 'WixHarmonyEditorBuild';
5
5
  export const EDIT_SITE_TOOL_NAME = 'EditSite';
6
6
  export const PULL_SITE_JOB_TOOL_NAME = 'pullSiteCreationJob';
7
7
  export const CLAIM_SITE_TOOL_NAME = 'ClaimAnonymousSite';
@@ -15,7 +15,7 @@ export const PICK_IMAGES_TOOL_NAME = 'PickImagesForWixMedia';
15
15
  export const MEDIA_UPLOAD_URL_TOOL_NAME = 'CreateWixMediaUploadUrl';
16
16
  /** The Wix business features that send a site to StartHeadlessBuild: what wix-headless-kit ships. */
17
17
  export const WIX_BUSINESS_FEATURES = 'a store, bookings or appointments, rentals, events and tickets, a restaurant menu with online orders or reservations, a blog, a portfolio, pricing plans and memberships, donations, Wix Forms, members and login, a CMS, payments';
18
- /** WixSiteBuilder's and SearchSiteTemplates' answer when the agent says the user did not ask for them. */
18
+ /** WixHarmonyEditorBuild's and SearchSiteTemplates' answer when the agent says the user did not ask for them. */
19
19
  /**
20
20
  * The widget payload of a refused editor call: the gallery and builder widgets render nothing
21
21
  * for it, since the host mounts them for every call of their tool.
@@ -23,7 +23,7 @@ export const WIX_BUSINESS_FEATURES = 'a store, bookings or appointments, rentals
23
23
  export const NOT_REQUESTED_WIDGET_PAYLOAD = { notRequested: true };
24
24
  export const EDITOR_NOT_REQUESTED_MESSAGE = `Nothing was created: the user did not ask for this. A new site is written in code and uploaded: call StartDeployWebsite, or StartHeadlessBuild when it needs Wix business features. Don't offer Wix's editor or a template instead.`;
25
25
  /** WixSiteCreation's answer: it no longer opens a chooser, it sends the agent to the code path. */
26
- export const SITE_CREATION_ROUTE_MESSAGE = `A new site is written in code and uploaded. Call StartDeployWebsite now for the upload guide, or StartHeadlessBuild when the site needs Wix business features. Use WixSiteBuilder or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.`;
26
+ export const SITE_CREATION_ROUTE_MESSAGE = `A new site is written in code and uploaded. Call StartDeployWebsite now for the upload guide, or StartHeadlessBuild when the site needs Wix business features. Use WixHarmonyEditorBuild or SearchSiteTemplates only when the user asked for the Wix Harmony Editor, a template, Wix Studio or the Classic Editor.`;
27
27
  /** Fallback actions when the industry is generic or unclear. */
28
28
  export const SITE_CREATION_DEFAULT_ACTIONS = 'add products, set up bookings, and adjust your content';
29
29
  /**
@@ -13,7 +13,7 @@ export function addPullSiteJobTool(server, authStrategy, options) {
13
13
  title: 'Pull Site Job',
14
14
  description: isWidgetClient
15
15
  ? 'Poll the status of a site creation or editing job. Do not call this tool unless the user explicitly asks for status polling.'
16
- : 'Poll the status of a site creation or editing job. Call this tool with the jobId returned by WixSiteBuilder or EditSite to check if the site build is complete. When the job status is COMPLETED, the response includes the previewUrl and editorUrl.',
16
+ : 'Poll the status of a site creation or editing job. Call this tool with the jobId returned by WixHarmonyEditorBuild or EditSite to check if the site build is complete. When the job status is COMPLETED, the response includes the previewUrl and editorUrl.',
17
17
  inputSchema: {
18
18
  jobId: z.string().describe('The job ID to pull')
19
19
  },