@wix/mcp 1.0.89 → 1.0.90

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.
@@ -107,7 +107,8 @@ export const paramDescriptions = {
107
107
  },
108
108
  ReadFullDocsArticle: {
109
109
  articleUrl: 'The URL of the docs article or method article to fetch. Should be something like https://dev.wix.com/docs/.../...' +
110
- ' For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include the query param ?apiView=SDK (e.g. https://dev.wix.com/docs/...?apiView=SDK).'
110
+ ' For REST docs, use the URL as-is. For SDK docs, the URL SHOULD include the query param ?apiView=SDK (e.g. https://dev.wix.com/docs/...?apiView=SDK).' +
111
+ ' Wix skills pages (https://www.wix.com/skills/..., https://dev.wix.com/docs/skills/...) are read as-is, including their referenced files.'
111
112
  },
112
113
  ReadFullDocsMethodSchema: {
113
114
  articleUrl: 'The URL of the documentation to fetch. Should be something like https://dev.wix.com/docs/.../...' +
@@ -0,0 +1,13 @@
1
+ import { WixMcpServer } from '../wix-mcp-server.js';
2
+ export declare const DEPLOY_WEBSITE_TOOL_NAME = "DeployWebsite";
3
+ export declare const DEPLOY_WEBSITE_RECIPE_URL = "https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md";
4
+ export declare const DROP_PAGE_URL = "https://www.wix.com/headless/drop?utm_campaign=mcp";
5
+ export declare function deployWebsiteResponse(recipe: string): string;
6
+ export declare const DEPLOY_WEBSITE_POINTER_RESPONSE: string;
7
+ export interface DeployWebsiteFetchOptions {
8
+ skillsPortalUrl?: string;
9
+ repo?: string;
10
+ commitHash?: string;
11
+ }
12
+ export declare function addDeployWebsiteTool(server: WixMcpServer, disableTools?: string[], toolDescriptions?: Record<string, string>, fetchOptions?: DeployWebsiteFetchOptions): void;
13
+ //# sourceMappingURL=deploy-website.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { defaultToolDescriptions } from '../config/default-config.js';
2
+ import { fetchArticleContent } from './fetch-article.js';
3
+ export const DEPLOY_WEBSITE_TOOL_NAME = 'DeployWebsite';
4
+ export const DEPLOY_WEBSITE_RECIPE_URL = 'https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md';
5
+ export const DROP_PAGE_URL = 'https://www.wix.com/headless/drop?utm_campaign=mcp';
6
+ const FALLBACK_LINE = `Only if you cannot run it (you can't reach the file bytes, or a step fails and you can't fix it): send the user to ${DROP_PAGE_URL} — give that URL exactly as written, keeping the utm_campaign=mcp parameter.`;
7
+ // The tool exists to take the agent's attention at routing time; the work itself
8
+ // is the recipe, so the response IS the recipe — fetched live and returned whole,
9
+ // so the agent never has to decide whether to go read it.
10
+ export function deployWebsiteResponse(recipe) {
11
+ return [
12
+ `The following is the content of ${DEPLOY_WEBSITE_RECIPE_URL}. Follow it end to end and do the publishing yourself — do not hand the user instructions.`,
13
+ FALLBACK_LINE,
14
+ '---',
15
+ recipe.trim()
16
+ ].join('\n\n');
17
+ }
18
+ // When the recipe can't be fetched, the pointer still routes correctly.
19
+ export const DEPLOY_WEBSITE_POINTER_RESPONSE = [
20
+ `Read and follow this recipe end to end: ${DEPLOY_WEBSITE_RECIPE_URL}`,
21
+ 'It covers creating the site and uploading the files (with or without a Wix login), updating a site deployed this way, and implementing a backend on it (stores, bookings, CMS, members) as a Wix Headless project. Do the publishing yourself — do not hand the user instructions.',
22
+ FALLBACK_LINE
23
+ ].join('\n\n');
24
+ export function addDeployWebsiteTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
25
+ if (disableTools.includes(DEPLOY_WEBSITE_TOOL_NAME)) {
26
+ return;
27
+ }
28
+ server.tool(DEPLOY_WEBSITE_TOOL_NAME, toolDescriptions?.[DEPLOY_WEBSITE_TOOL_NAME] ??
29
+ defaultToolDescriptions[DEPLOY_WEBSITE_TOOL_NAME], {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (_args, { httpClient }) => {
30
+ let text = DEPLOY_WEBSITE_POINTER_RESPONSE;
31
+ try {
32
+ // Same path ReadFullDocsArticle takes for this page: the rawdocs article
33
+ // API, addressed without the .md suffix, header scaffolding stripped.
34
+ const recipe = await fetchArticleContent(httpClient, DEPLOY_WEBSITE_RECIPE_URL.replace(/\.md$/, ''), 'article', true, fetchOptions.skillsPortalUrl, fetchOptions.repo, fetchOptions.commitHash);
35
+ if (recipe && recipe.trim()) {
36
+ text = deployWebsiteResponse(recipe);
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.warn(`[DeployWebsite] recipe fetch failed, returning the pointer:`, error instanceof Error ? error.message : error);
41
+ }
42
+ return { content: [{ type: 'text', text }] };
43
+ });
44
+ }
45
+ //# sourceMappingURL=deploy-website.js.map
@@ -19,6 +19,8 @@ export interface ReadmeDocConfig {
19
19
  export interface DocsToolsOptions {
20
20
  getToKnowWixEnabled?: boolean;
21
21
  createWixBusinessGuideEnabled?: boolean;
22
+ /** DeployWebsite (publish ready-made site files) — an account-owner flow; off for visitor servers. */
23
+ deployWebsiteEnabled?: boolean;
22
24
  disableTools?: string[];
23
25
  /** Custom descriptions for tools, keyed by tool name */
24
26
  toolDescriptions?: Record<string, string>;
@@ -2,12 +2,13 @@ import { z } from 'zod';
2
2
  import { runSemanticSearchAndFormat } from './semanticSearch.js';
3
3
  import { logger } from '../infra/logger.js';
4
4
  import { captureException } from '@sentry/node';
5
- import { fetchArticleContent } from './fetch-article.js';
5
+ import { fetchArticleContent, isSkillsRegistryUrl } 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
8
  import { isQueryOrSearchEndpoint, QUERY_CAPABILITY_MARKER } from './searchFormatting.js';
9
9
  import { addGetToKnowTools, addGetSiteContextTool } from './get-to-know.js';
10
10
  import { addCreateWixBusinessGuideTools } from './create-site-guide.js';
11
+ import { addDeployWebsiteTool } from './deploy-website.js';
11
12
  import { defaultToolDescriptions } from '../config/default-config.js';
12
13
  import { desc } from '../config/param-descriptions.js';
13
14
  import dedent from 'dedent';
@@ -67,7 +68,7 @@ export const addDocsTools = (server, allowedTools = [
67
68
  'CLI',
68
69
  'OVERVIEW'
69
70
  ], options = {}) => {
70
- const { getToKnowWixEnabled = false, createWixBusinessGuideEnabled = true, disableTools, toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, skillsRepo, repo, skillsPr, commitHash, skillsTags, dynamicContextEnabled, cliEnabled = false, usePlatformizedSearch = true, usePlatformizedBrowse = true } = options;
71
+ const { getToKnowWixEnabled = false, createWixBusinessGuideEnabled = true, deployWebsiteEnabled = true, disableTools, toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, skillsRepo, repo, skillsPr, commitHash, skillsTags, dynamicContextEnabled, cliEnabled = false, usePlatformizedSearch = true, usePlatformizedBrowse = true } = options;
71
72
  const effectiveRepo = repo ?? skillsRepo;
72
73
  const effectiveCommitHash = commitHash ?? skillsPr;
73
74
  // Helper to get description (custom or default)
@@ -123,6 +124,15 @@ export const addDocsTools = (server, allowedTools = [
123
124
  !disableTools?.includes('CreateWixBusinessGuide')) {
124
125
  addCreateWixBusinessGuideTools(server, disableTools || [], toolDescriptions, cliEnabled);
125
126
  }
127
+ // On by default: it is a routing slot, not a docs source — the agent with
128
+ // ready-made site files must find it next to the site-creation tools.
129
+ if (deployWebsiteEnabled) {
130
+ addDeployWebsiteTool(server, disableTools || [], toolDescriptions, {
131
+ skillsPortalUrl,
132
+ repo: effectiveRepo,
133
+ commitHash: effectiveCommitHash
134
+ });
135
+ }
126
136
  // WDS Documentation
127
137
  if (allowedTools.includes('WDS') &&
128
138
  !disableTools?.includes('SearchWixWDSDocumentation')) {
@@ -622,17 +632,20 @@ export const addDocsTools = (server, allowedTools = [
622
632
  articleUrl: z.string().url().describe(d.articleUrl)
623
633
  };
624
634
  })(), { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async ({ articleUrl }, { httpClient }) => {
625
- // URL validation: only allow known docs prefixes
635
+ // URL validation: known docs prefixes, or a Wix skills registry page
636
+ // (https://www.wix.com/skills/..., https://dev.wix.com/docs/skills/...).
626
637
  const validPrefixes = ['https://dev.wix.com/docs/'];
627
638
  if (skillsPortalUrl)
628
639
  validPrefixes.push(skillsPortalUrl);
629
- if (!validPrefixes.some((p) => articleUrl.startsWith(p))) {
640
+ const isSkillsPage = isSkillsRegistryUrl(articleUrl);
641
+ if (!isSkillsPage &&
642
+ !validPrefixes.some((p) => articleUrl.startsWith(p))) {
630
643
  return {
631
644
  isError: true,
632
645
  content: [
633
646
  {
634
647
  type: 'text',
635
- text: `Invalid article URL: must start with https://dev.wix.com/docs/. Got: "${articleUrl}"`
648
+ text: `Invalid article URL: must start with https://dev.wix.com/docs/, or be a Wix skills page (https://www.wix.com/skills/...). Got: "${articleUrl}"`
636
649
  }
637
650
  ]
638
651
  };
@@ -640,8 +653,9 @@ export const addDocsTools = (server, allowedTools = [
640
653
  // Every docs page has a .md twin whose menu pages link children WITH
641
654
  // the .md suffix, so agents that navigated the .md tree pass .md URLs
642
655
  // here — which 404 in get-article-content. Read the canonical page.
643
- // (dev.wix.com only: on the skills portal .md is the real path.)
644
- if (articleUrl.startsWith('https://dev.wix.com/docs/')) {
656
+ // (dev.wix.com docs only: on the skills registry .md is the real path.)
657
+ if (!isSkillsPage &&
658
+ articleUrl.startsWith('https://dev.wix.com/docs/')) {
645
659
  articleUrl = articleUrl.replace(/\.md$/, '');
646
660
  }
647
661
  try {
@@ -35,5 +35,6 @@ export declare function fetchRecipesIndex(): Promise<string>;
35
35
  * ```
36
36
  */
37
37
  export declare function fetchArticle(articleUrl: string): Promise<string>;
38
+ export declare function isSkillsRegistryUrl(url: string): boolean;
38
39
  export declare function fetchArticleContent(httpClient: HttpClient, articleUrl: string, mode: 'article' | 'methodSchema' | 'raw', stripHeader?: boolean, skillsPortalUrl?: string, repo?: string, commitHash?: string, fetchMcpWhiteList?: boolean): Promise<string>;
39
40
  //# sourceMappingURL=fetch-article.d.ts.map
@@ -50,6 +50,38 @@ export async function fetchArticle(articleUrl) {
50
50
  // Internal implementation
51
51
  // ---------------------------------------------------------------------------
52
52
  const DOCS_URL_PREFIX = 'https://dev.wix.com/docs/';
53
+ /**
54
+ * The Wix skills registry serves agent skills (SKILL.md and bundled files)
55
+ * as raw markdown straight from the `wix/skills` repo, on every host the
56
+ * gateway maps to it. Pages there are not docs articles: the rawdocs
57
+ * get-article-content API does not know them, and `.md` is a real path, so
58
+ * they are fetched as-is.
59
+ */
60
+ const SKILLS_REGISTRY_URL = new RegExp('^https://(?:dev\\.wix\\.com(?:/docs)?|www\\.wix\\.com(?:/velo/reference)?)/skills(?:[/.?#]|$)');
61
+ export function isSkillsRegistryUrl(url) {
62
+ return SKILLS_REGISTRY_URL.test(url.trim());
63
+ }
64
+ async function fetchSkillsRegistryPage(httpClient, url) {
65
+ let data;
66
+ try {
67
+ ({ data } = await httpClient.get(url, {
68
+ headers: { accept: 'text/markdown, text/plain;q=0.9, */*;q=0.1' }
69
+ }));
70
+ }
71
+ catch (error) {
72
+ const { message, status } = getHttpErrorDetails(error);
73
+ const statusSuffix = status ? ` (status ${status})` : '';
74
+ throw new Error(`${message}${statusSuffix}`);
75
+ }
76
+ if (typeof data !== 'string') {
77
+ // The manifest endpoint is JSON; anything else is text.
78
+ return JSON.stringify(data, null, 2);
79
+ }
80
+ if (!data.trim()) {
81
+ throw new Error('Article content is empty');
82
+ }
83
+ return data;
84
+ }
53
85
  function getResponseErrorMessage(data) {
54
86
  if (!data) {
55
87
  return undefined;
@@ -152,10 +184,16 @@ export async function fetchArticleContent(httpClient, articleUrl, mode, stripHea
152
184
  }
153
185
  catch (error) {
154
186
  console.warn(`[fetch] Override failed for ${effectiveUrl}, falling back to ${articleUrl}:`, error instanceof Error ? error.message : error);
155
- // Fall through to normal fetch
156
- content = await fetchArticleContentFromDigor(httpClient, articleUrl, mode, stripHeader);
187
+ // Fall through to the normal fetch for this URL: the registry for a
188
+ // skills page (rawdocs cannot serve those), rawdocs otherwise.
189
+ content = isSkillsRegistryUrl(articleUrl)
190
+ ? await fetchSkillsRegistryPage(httpClient, articleUrl)
191
+ : await fetchArticleContentFromDigor(httpClient, articleUrl, mode, stripHeader);
157
192
  }
158
193
  }
194
+ else if (isSkillsRegistryUrl(articleUrl)) {
195
+ content = await fetchSkillsRegistryPage(httpClient, articleUrl);
196
+ }
159
197
  else if (mode === 'raw') {
160
198
  let parsedUrl;
161
199
  try {
@@ -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 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.");
89
+ return errorResult("Invalid design URL — this tool only accepts the claudeusercontent.com URL produced by Claude Design. For a page or site that exists in any other form (the user's files, a build, a zip, another builder's export, a page you made), do not retry this tool — call DeployWebsite, which returns the recipe to publish the files yourself. If that tool isn't available: read https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md and publish them yourself, or send the user to https://www.wix.com/headless/drop?utm_campaign=mcp.");
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 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.");
93
+ return errorResult("Invalid design URL — this tool only accepts the claudeusercontent.com URL produced by Claude Design. For a page or site that exists in any other form (the user's files, a build, a zip, another builder's export, a page you made), do not retry this tool — call DeployWebsite, which returns the recipe to publish the files yourself. If that tool isn't available: read https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md and publish them yourself, or send the user to https://www.wix.com/headless/drop?utm_campaign=mcp.");
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
@@ -127,6 +127,8 @@ export interface ReadmeDocConfig {
127
127
  export interface DocsToolsOptions {
128
128
  getToKnowWixEnabled?: boolean;
129
129
  createWixBusinessGuideEnabled?: boolean;
130
+ /** DeployWebsite (publish ready-made site files) — an account-owner flow; off for visitor servers. */
131
+ deployWebsiteEnabled?: boolean;
130
132
  disableTools?: string[];
131
133
  /** Custom descriptions for tools, keyed by tool name */
132
134
  toolDescriptions?: Record<string, string>;
@@ -127,6 +127,8 @@ export interface ReadmeDocConfig {
127
127
  export interface DocsToolsOptions {
128
128
  getToKnowWixEnabled?: boolean;
129
129
  createWixBusinessGuideEnabled?: boolean;
130
+ /** DeployWebsite (publish ready-made site files) — an account-owner flow; off for visitor servers. */
131
+ deployWebsiteEnabled?: boolean;
130
132
  disableTools?: string[];
131
133
  /** Custom descriptions for tools, keyed by tool name */
132
134
  toolDescriptions?: Record<string, string>;