@wix/mcp 1.0.94 → 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.
- package/README.md +2 -2
- package/build/bin-standalone.js +1457 -332
- package/build/cjs/index.cjs +3379 -1333
- package/build/cjs/toolkit.cjs +2158 -373
- package/build/dts/api-call/headless-release.d.ts +56 -0
- package/build/dts/api-call/headless-release.js +135 -0
- package/build/dts/api-call/index.d.ts +9 -1
- package/build/dts/api-call/index.js +36 -8
- package/build/dts/api-call/site-files.d.ts +14 -0
- package/build/dts/api-call/site-files.js +106 -10
- package/build/dts/api-call/upload-headless-website-files.d.ts +117 -5
- package/build/dts/api-call/upload-headless-website-files.js +565 -20
- package/build/dts/api-call/upload-image-tool.d.ts +2 -0
- package/build/dts/api-call/upload-image-tool.js +8 -4
- package/build/dts/bin.d.ts +1 -1
- package/build/dts/bin.js +3 -1
- package/build/dts/code-mode/attachments.d.ts +9 -6
- package/build/dts/code-mode/attachments.js +23 -16
- package/build/dts/code-mode/index.d.ts +6 -1
- package/build/dts/code-mode/index.js +32 -12
- package/build/dts/code-mode/truncate-response.js +5 -1
- package/build/dts/config/default-config.d.ts +35 -5
- package/build/dts/config/default-config.js +339 -182
- package/build/dts/config/file-params.d.ts +22 -0
- package/build/dts/config/file-params.js +24 -0
- package/build/dts/config/param-descriptions.d.ts +14 -2
- package/build/dts/config/param-descriptions.js +33 -14
- package/build/dts/constants.d.ts +1 -0
- package/build/dts/constants.js +1 -0
- package/build/dts/docs/docs.d.ts +12 -1
- package/build/dts/docs/docs.js +274 -43
- package/build/dts/docs/fetch-article.d.ts +21 -0
- package/build/dts/docs/fetch-article.js +66 -4
- package/build/dts/docs/get-to-know.d.ts +7 -2
- package/build/dts/docs/get-to-know.js +36 -14
- package/build/dts/docs/guessed-url.d.ts +41 -0
- package/build/dts/docs/guessed-url.js +185 -0
- package/build/dts/docs/mintlify-docs.d.ts +121 -0
- package/build/dts/docs/mintlify-docs.js +391 -0
- package/build/dts/docs/semanticSearch.d.ts +2 -1
- package/build/dts/docs/semanticSearch.js +14 -7
- package/build/dts/docs/start-deploy-website.d.ts +7 -6
- package/build/dts/docs/start-deploy-website.js +13 -8
- package/build/dts/docs/start-headless-build.d.ts +8 -10
- package/build/dts/docs/start-headless-build.js +14 -28
- package/build/dts/index.d.ts +2 -2
- package/build/dts/index.js +2 -2
- package/build/dts/infra/redact-personal-data.d.ts +4 -0
- package/build/dts/infra/redact-personal-data.js +148 -0
- package/build/dts/site-widget-tools/constants.d.ts +50 -2
- package/build/dts/site-widget-tools/constants.js +65 -8
- package/build/dts/site-widget-tools/index.d.ts +1 -0
- package/build/dts/site-widget-tools/index.js +1 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
- package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
- package/build/dts/site-widget-tools/search-site-templates-tool/index.js +25 -2
- package/build/dts/site-widget-tools/site-builder-tool/index.js +35 -3
- package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
- package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +26 -12
- package/build/dts/support/index.d.ts +4 -0
- package/build/dts/support/index.js +13 -7
- package/build/dts/tool-utils.d.ts +8 -1
- package/build/dts/tool-utils.js +26 -2
- package/build/dts/toolkit-entry.d.cts +15 -10
- package/build/dts/toolkit-entry.d.ts +15 -10
- package/build/dts/toolkit.d.ts +2 -2
- package/build/dts/toolkit.js +21 -8
- package/build/dts/wix-mcp-server.d.ts +10 -1
- package/build/dts/wix-mcp-server.js +25 -7
- package/build/dts/wixel/constants.d.ts +8 -1
- package/build/dts/wixel/constants.js +8 -1
- package/build/dts/wixel/index.d.ts +1 -1
- package/build/dts/wixel/index.js +2 -2
- package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
- package/build/dts/wixel/wixel-a2a-client.js +166 -0
- package/build/dts/wixel/wixel-bi.d.ts +2 -1
- package/build/dts/wixel/wixel-bi.js +2 -1
- package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
- package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
- package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
- package/build/dts/wixel/wixel-session-tools.js +494 -599
- package/build/esm/index.js +3422 -1388
- package/build/esm/toolkit.js +2158 -373
- package/package.json +4 -4
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { logger } from '../infra/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mintlify docs, `mintlify-links` mode (wix-private/docs:
|
|
4
|
+
* documentation/mintlify-migration/tasks/mcp-pipeline.md).
|
|
5
|
+
*
|
|
6
|
+
* The KBs and every backend stay as they are. With `docsSite: 'mintlify-links'`,
|
|
7
|
+
* the docs tools resolve each dev.wix.com docs link in their output to its page
|
|
8
|
+
* on the Mintlify site, and ReadFullDocsArticle reads pages from Mintlify's
|
|
9
|
+
* `.md`. Off unless a caller passes the option.
|
|
10
|
+
*/
|
|
11
|
+
export const MINTLIFY_LINKS_DOCS_SITE = 'mintlify-links';
|
|
12
|
+
/** Stage 2: the same, with search on the Mintlify KBs indexed from the Mintlify site. */
|
|
13
|
+
export const MINTLIFY_DOCS_SITE = 'mintlify';
|
|
14
|
+
export const MINTLIFY_HOST = 'https://wix-internal.mintlify.site';
|
|
15
|
+
const MINTLIFY_HOSTNAMES = new Set([
|
|
16
|
+
'wix-internal.mintlify.site',
|
|
17
|
+
'wix-internal.mintlify.app'
|
|
18
|
+
]);
|
|
19
|
+
const DEV_WIX_DOCS = 'https://dev.wix.com/docs';
|
|
20
|
+
// dev.wix.com portal slug -> Mintlify tab base, where the two differ.
|
|
21
|
+
const PORTAL_SLUG_TO_TAB = {
|
|
22
|
+
rest: 'api-reference',
|
|
23
|
+
'mintlify-sandbox': 'sandbox'
|
|
24
|
+
};
|
|
25
|
+
const TAB_TO_PORTAL_SLUG = {
|
|
26
|
+
sandbox: 'mintlify-sandbox'
|
|
27
|
+
};
|
|
28
|
+
// The tabs the Mintlify site serves. A dev.wix.com link into any other portal
|
|
29
|
+
// (sdk, velo, build-apps, ...) has no page there yet.
|
|
30
|
+
const MINTLIFY_TABS = new Set(['api-reference', 'sandbox', 'kb']);
|
|
31
|
+
// The skills registry is served on dev.wix.com/docs/skills and is not part of
|
|
32
|
+
// the docs site, so its links are left alone.
|
|
33
|
+
const NON_DOCS_SLUGS = new Set(['skills', 'kb-only', 'api']);
|
|
34
|
+
const CHECK_TTL_MS = 10 * 60 * 1000;
|
|
35
|
+
// Bounds the page-check cache, so many distinct paths can't grow it without limit.
|
|
36
|
+
export const MAX_CHECK_ENTRIES = 5000;
|
|
37
|
+
const CHECK_TIMEOUT_MS = 5000;
|
|
38
|
+
const MAX_REDIRECTS = 3;
|
|
39
|
+
const CHECK_CONCURRENCY = 8;
|
|
40
|
+
export function isMintlifyDocsSite(docsSite) {
|
|
41
|
+
return (docsSite === MINTLIFY_LINKS_DOCS_SITE || docsSite === MINTLIFY_DOCS_SITE);
|
|
42
|
+
}
|
|
43
|
+
// docs-search-server document types that read the Mintlify KBs instead of today's.
|
|
44
|
+
const MINTLIFY_DOCUMENT_TYPES = {
|
|
45
|
+
REST: ['MINTLIFY_REST'], // includes the skills, as REST + SKILLS does today
|
|
46
|
+
SDK: ['MINTLIFY_SDK']
|
|
47
|
+
};
|
|
48
|
+
/** The Mintlify document types a search tool uses in stage 2, or undefined to search as today. */
|
|
49
|
+
export function mintlifyDocumentTypes(docsSite, toolName) {
|
|
50
|
+
return docsSite === MINTLIFY_DOCS_SITE
|
|
51
|
+
? MINTLIFY_DOCUMENT_TYPES[toolName]
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
export function isMintlifyUrl(url) {
|
|
55
|
+
try {
|
|
56
|
+
return MINTLIFY_HOSTNAMES.has(new URL(url).hostname);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The URL prefixes the docs tools accept in Mintlify mode, besides dev.wix.com. */
|
|
63
|
+
export const MINTLIFY_URL_PREFIXES = [...MINTLIFY_HOSTNAMES].map((host) => `https://${host}/`);
|
|
64
|
+
function cleanPath(pathname) {
|
|
65
|
+
const path = pathname.replace(/\.md$/, '').replace(/\/+$/, '');
|
|
66
|
+
return path === '' ? '/' : path;
|
|
67
|
+
}
|
|
68
|
+
function flavourOf(url) {
|
|
69
|
+
return url.searchParams.get('apiView')?.toUpperCase() === 'SDK'
|
|
70
|
+
? 'sdk'
|
|
71
|
+
: 'rest';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Maps a dev.wix.com docs URL, or a Mintlify URL on any alias host, to its
|
|
75
|
+
* Mintlify page by rule: drop `/docs`, map the portal slug to its tab, drop
|
|
76
|
+
* the query, the anchor and `.md`. Returns null for anything else.
|
|
77
|
+
*/
|
|
78
|
+
export function toMintlifyCandidate(rawUrl) {
|
|
79
|
+
let url;
|
|
80
|
+
try {
|
|
81
|
+
url = new URL(rawUrl);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (MINTLIFY_HOSTNAMES.has(url.hostname)) {
|
|
87
|
+
const path = cleanPath(url.pathname);
|
|
88
|
+
return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
|
|
89
|
+
}
|
|
90
|
+
if (url.hostname !== 'dev.wix.com' || !url.pathname.startsWith('/docs/')) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const segments = cleanPath(url.pathname.slice('/docs'.length))
|
|
94
|
+
.split('/')
|
|
95
|
+
.filter(Boolean);
|
|
96
|
+
if (segments.length === 0 || NON_DOCS_SLUGS.has(segments[0])) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
segments[0] = PORTAL_SLUG_TO_TAB[segments[0]] ?? segments[0];
|
|
100
|
+
const path = `/${segments.join('/')}`;
|
|
101
|
+
return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Maps a Mintlify URL back to its dev.wix.com docs URL, keeping `?apiView=SDK`.
|
|
105
|
+
* For the REST portal the paths are the same, so the backends that only know
|
|
106
|
+
* dev.wix.com (ingvar, the docs menu) can serve it.
|
|
107
|
+
*/
|
|
108
|
+
export function toDevWixUrl(rawUrl) {
|
|
109
|
+
const url = new URL(rawUrl);
|
|
110
|
+
const segments = cleanPath(url.pathname).split('/').filter(Boolean);
|
|
111
|
+
if (segments.length > 0) {
|
|
112
|
+
segments[0] = TAB_TO_PORTAL_SLUG[segments[0]] ?? segments[0];
|
|
113
|
+
}
|
|
114
|
+
const query = flavourOf(url) === 'sdk' ? '?apiView=SDK' : '';
|
|
115
|
+
return `${DEV_WIX_DOCS}/${segments.join('/')}${query}`;
|
|
116
|
+
}
|
|
117
|
+
const checkCache = new Map();
|
|
118
|
+
/** Caches a page check, dropping expired entries, then the oldest, once full. */
|
|
119
|
+
function rememberCheck(path, result) {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
checkCache.delete(path);
|
|
122
|
+
if (checkCache.size >= MAX_CHECK_ENTRIES) {
|
|
123
|
+
for (const [key, entry] of checkCache) {
|
|
124
|
+
if (entry.expires <= now)
|
|
125
|
+
checkCache.delete(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
while (checkCache.size >= MAX_CHECK_ENTRIES) {
|
|
129
|
+
const oldest = checkCache.keys().next().value;
|
|
130
|
+
if (oldest === undefined)
|
|
131
|
+
break;
|
|
132
|
+
checkCache.delete(oldest);
|
|
133
|
+
}
|
|
134
|
+
checkCache.set(path, { result, expires: now + CHECK_TTL_MS });
|
|
135
|
+
}
|
|
136
|
+
/** For tests. */
|
|
137
|
+
export function mintlifyCheckCacheSize() {
|
|
138
|
+
return checkCache.size;
|
|
139
|
+
}
|
|
140
|
+
/** For tests. */
|
|
141
|
+
export function clearMintlifyCheckCache() {
|
|
142
|
+
checkCache.clear();
|
|
143
|
+
}
|
|
144
|
+
async function headOnce(url) {
|
|
145
|
+
return fetch(url, {
|
|
146
|
+
method: 'HEAD',
|
|
147
|
+
redirect: 'manual',
|
|
148
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Asks the Mintlify site whether a page exists, following its own redirects
|
|
153
|
+
* (moves, and containers that send you to their first page) without
|
|
154
|
+
* following them blindly: a page is a 200 at the end of at most 3 hops.
|
|
155
|
+
*/
|
|
156
|
+
export async function checkMintlifyPage(path) {
|
|
157
|
+
const cached = checkCache.get(path);
|
|
158
|
+
if (cached && cached.expires > Date.now()) {
|
|
159
|
+
return cached.result;
|
|
160
|
+
}
|
|
161
|
+
if (cached)
|
|
162
|
+
checkCache.delete(path);
|
|
163
|
+
let result;
|
|
164
|
+
try {
|
|
165
|
+
let current = path;
|
|
166
|
+
let hops = 0;
|
|
167
|
+
for (;;) {
|
|
168
|
+
const response = await headOnce(`${MINTLIFY_HOST}${current}`);
|
|
169
|
+
if (response.status === 200) {
|
|
170
|
+
result = {
|
|
171
|
+
outcome: hops === 0 ? 'identity' : 'redirect',
|
|
172
|
+
path: current
|
|
173
|
+
};
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
const location = response.headers.get('location');
|
|
177
|
+
if (response.status >= 300 &&
|
|
178
|
+
response.status < 400 &&
|
|
179
|
+
location &&
|
|
180
|
+
hops < MAX_REDIRECTS) {
|
|
181
|
+
current = cleanPath(new URL(location, MINTLIFY_HOST).pathname);
|
|
182
|
+
hops++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
result = { outcome: response.status === 404 ? 'missing' : 'error' };
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
logger.error(`[mintlify] page check failed for ${path}: ${error}`);
|
|
191
|
+
result = { outcome: 'error' };
|
|
192
|
+
}
|
|
193
|
+
rememberCheck(path, result);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolves one docs link to its Mintlify page. With `verify: false` the
|
|
198
|
+
* mapping is by rule only, which suits menu links (the menu and the Mintlify
|
|
199
|
+
* navigation come from the same Portal Service tree).
|
|
200
|
+
*/
|
|
201
|
+
export async function resolveDocsLink(rawUrl, { verify = true } = {}) {
|
|
202
|
+
const candidate = toMintlifyCandidate(rawUrl);
|
|
203
|
+
if (!candidate) {
|
|
204
|
+
return { outcome: 'not-docs', url: rawUrl };
|
|
205
|
+
}
|
|
206
|
+
const tab = candidate.path.split('/')[1];
|
|
207
|
+
if (!MINTLIFY_TABS.has(tab)) {
|
|
208
|
+
return { outcome: 'other-portal', url: rawUrl };
|
|
209
|
+
}
|
|
210
|
+
const query = candidate.flavour === 'sdk' ? '?apiView=SDK' : '';
|
|
211
|
+
if (!verify) {
|
|
212
|
+
return { outcome: 'identity', url: `${candidate.url}${query}` };
|
|
213
|
+
}
|
|
214
|
+
const check = await checkMintlifyPage(candidate.path);
|
|
215
|
+
if (check.path) {
|
|
216
|
+
return {
|
|
217
|
+
outcome: check.outcome,
|
|
218
|
+
url: `${MINTLIFY_HOST}${check.path}${query}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return { outcome: check.outcome, url: rawUrl };
|
|
222
|
+
}
|
|
223
|
+
// A docs link inside text: markdown targets, bare URLs and "fallback::" forms.
|
|
224
|
+
// Stops at whitespace and markdown/HTML delimiters.
|
|
225
|
+
const DOCS_LINK_PATTERN = /https:\/\/(?:dev\.wix\.com\/docs|wix-internal\.mintlify\.(?:site|app))\/[^\s)\]"'<>`]*/g;
|
|
226
|
+
function trimTrailingPunctuation(url) {
|
|
227
|
+
return url.replace(/[.,;:!?*]+$/, '');
|
|
228
|
+
}
|
|
229
|
+
// A Mintlify page link inside text, with the same delimiters as DOCS_LINK_PATTERN.
|
|
230
|
+
const MINTLIFY_LINK_PATTERN = /https:\/\/wix-internal\.mintlify\.(?:site|app)\/[^\s)\]"'<>`]*/g;
|
|
231
|
+
/**
|
|
232
|
+
* Maps every Mintlify page link in `text` back to its dev.wix.com docs URL, keeping
|
|
233
|
+
* `?apiView=SDK` and the anchor. Run before {@link rewriteDocsLinks} on text whose
|
|
234
|
+
* Mintlify links came by rule rather than from a checked page (SearchWixAPISpec's
|
|
235
|
+
* index), so a page the site doesn't have ends up on dev.wix.com, not a dead link.
|
|
236
|
+
*/
|
|
237
|
+
export function toDevWixHostText(text) {
|
|
238
|
+
return text.replace(MINTLIFY_LINK_PATTERN, (match) => {
|
|
239
|
+
const link = trimTrailingPunctuation(match);
|
|
240
|
+
let url;
|
|
241
|
+
try {
|
|
242
|
+
url = new URL(link);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return match;
|
|
246
|
+
}
|
|
247
|
+
// Only docs pages: other paths on the host (llms.txt, assets) have no
|
|
248
|
+
// dev.wix.com twin, and rewriteDocsLinks leaves them as they are.
|
|
249
|
+
if (!MINTLIFY_TABS.has(url.pathname.split('/')[1] ?? ''))
|
|
250
|
+
return match;
|
|
251
|
+
return toDevWixUrl(link) + url.hash + match.slice(link.length);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
255
|
+
const results = new Array(items.length);
|
|
256
|
+
let next = 0;
|
|
257
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
258
|
+
while (next < items.length) {
|
|
259
|
+
const index = next++;
|
|
260
|
+
results[index] = await fn(items[index]);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
await Promise.all(workers);
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Rewrites every docs link in `text` to its Mintlify page. Links with no
|
|
268
|
+
* Mintlify page stay as they are, so the agent can still read them through
|
|
269
|
+
* today's backends. Logs one coverage line per call.
|
|
270
|
+
*/
|
|
271
|
+
export async function rewriteDocsLinks(text, { verify = true, label = 'text' } = {}) {
|
|
272
|
+
const found = [
|
|
273
|
+
...new Set((text.match(DOCS_LINK_PATTERN) ?? []).map(trimTrailingPunctuation))
|
|
274
|
+
];
|
|
275
|
+
const stats = {
|
|
276
|
+
links: found.length,
|
|
277
|
+
byOutcome: {},
|
|
278
|
+
misses: []
|
|
279
|
+
};
|
|
280
|
+
if (found.length === 0) {
|
|
281
|
+
return { text, stats };
|
|
282
|
+
}
|
|
283
|
+
const resolved = await mapWithConcurrency(found, CHECK_CONCURRENCY, (url) => resolveDocsLink(url, { verify }));
|
|
284
|
+
const replacements = new Map();
|
|
285
|
+
found.forEach((url, i) => {
|
|
286
|
+
const { outcome, url: target } = resolved[i];
|
|
287
|
+
stats.byOutcome[outcome] = (stats.byOutcome[outcome] ?? 0) + 1;
|
|
288
|
+
if (outcome === 'identity' || outcome === 'redirect') {
|
|
289
|
+
if (target !== url)
|
|
290
|
+
replacements.set(url, target);
|
|
291
|
+
}
|
|
292
|
+
else if (outcome !== 'not-docs') {
|
|
293
|
+
stats.misses.push(`${outcome} ${url}`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
// Rewrite each match in place, so a link never changes through a shorter
|
|
297
|
+
// link it starts with.
|
|
298
|
+
const rewritten = text.replace(DOCS_LINK_PATTERN, (match) => {
|
|
299
|
+
const url = trimTrailingPunctuation(match);
|
|
300
|
+
const target = replacements.get(url);
|
|
301
|
+
return target ? target + match.slice(url.length) : match;
|
|
302
|
+
});
|
|
303
|
+
logger.log(`[mintlify] ${label}: ${stats.links} links ${JSON.stringify(stats.byOutcome)}` +
|
|
304
|
+
(stats.misses.length
|
|
305
|
+
? ` misses: ${stats.misses.slice(0, 10).join(' | ')}`
|
|
306
|
+
: ''));
|
|
307
|
+
return { text: rewritten, stats };
|
|
308
|
+
}
|
|
309
|
+
// The index banner Mintlify puts at the top of every .md page.
|
|
310
|
+
const INDEX_BANNER = /^> ## Documentation Index\n(?:>.*\n)*\n?/;
|
|
311
|
+
// Mintlify inlines the source of every imported snippet component
|
|
312
|
+
// (`export const WixEndpoint = (...) => { ... };`): code, not docs.
|
|
313
|
+
const INLINED_SNIPPET = /^export const \w+ = [\s\S]*?^};\n/gm;
|
|
314
|
+
const REST_TAB = /<Tab title="REST">([\s\S]*?)<\/Tab>/;
|
|
315
|
+
const SDK_TAB = /<Tab title="SDK">([\s\S]*?)<\/Tab>/;
|
|
316
|
+
/**
|
|
317
|
+
* Light cleanup of a method page's .md: drop inlined snippet source, and keep
|
|
318
|
+
* only the tab the reader asked for (REST, or SDK for `?apiView=SDK`). A
|
|
319
|
+
* method page carries both, which roughly doubles its size. Pages without
|
|
320
|
+
* exactly one REST and one SDK tab are left as they are.
|
|
321
|
+
*/
|
|
322
|
+
export function cleanMintlifyMarkdown(markdown, flavour) {
|
|
323
|
+
const text = markdown.replace(INLINED_SNIPPET, '');
|
|
324
|
+
const rest = text.match(REST_TAB);
|
|
325
|
+
const sdk = text.match(SDK_TAB);
|
|
326
|
+
if (!rest || !sdk || (text.match(/<Tab title=/g) ?? []).length !== 2) {
|
|
327
|
+
return text;
|
|
328
|
+
}
|
|
329
|
+
const start = text.lastIndexOf('<Tabs>', rest.index);
|
|
330
|
+
const end = text.indexOf('</Tabs>', sdk.index);
|
|
331
|
+
if (start === -1 || end === -1) {
|
|
332
|
+
return text;
|
|
333
|
+
}
|
|
334
|
+
const kept = (flavour === 'sdk' ? sdk : rest)[1];
|
|
335
|
+
const note = flavour === 'sdk'
|
|
336
|
+
? '_This is the SDK view. Drop `?apiView=SDK` from the URL for the REST view._'
|
|
337
|
+
: '_This is the REST view. Add `?apiView=SDK` to the URL for the JavaScript SDK view._';
|
|
338
|
+
return `${text.slice(0, start)}${note}\n\n${kept.trim()}\n${text.slice(end + '</Tabs>'.length)}`;
|
|
339
|
+
}
|
|
340
|
+
// A docs URL in tool text that the Mintlify site has: API Reference, or a `…` placeholder.
|
|
341
|
+
// Skills URLs (dev.wix.com/docs/skills) are the skills registry and stay.
|
|
342
|
+
const DOCS_URL_IN_TEXT = /https:\/\/dev\.wix\.com\/docs\/(?=api-reference\b|\.\.\.)/g;
|
|
343
|
+
/**
|
|
344
|
+
* Tool and parameter descriptions with their docs URLs on the Mintlify host. The
|
|
345
|
+
* model copies the examples' host when it cites a page, so with dev.wix.com in
|
|
346
|
+
* the descriptions it rewrites Mintlify links back to dev.wix.com.
|
|
347
|
+
*/
|
|
348
|
+
export function toMintlifyHostText(text) {
|
|
349
|
+
return text.replace(DOCS_URL_IN_TEXT, `${MINTLIFY_HOST}/`);
|
|
350
|
+
}
|
|
351
|
+
// A Markdown link or an href to a path on the same site: `](/…` or `href="/…`.
|
|
352
|
+
const SITE_RELATIVE_LINK = /(\]\(|href=["'])\/(?!\/)/g;
|
|
353
|
+
/**
|
|
354
|
+
* Mintlify's .md links the site's own pages by path (`[Order Object](/api-reference/…)`).
|
|
355
|
+
* Make them absolute, so the model doesn't have to guess the host (it picks dev.wix.com).
|
|
356
|
+
*/
|
|
357
|
+
export function absolutizeSiteLinks(markdown) {
|
|
358
|
+
return markdown.replace(SITE_RELATIVE_LINK, (_, open) => `${open}${MINTLIFY_HOST}/`);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Reads a page from the Mintlify site as Markdown. Returns null when the site
|
|
362
|
+
* has no page for the URL, so the caller can fall back to today's backend.
|
|
363
|
+
*/
|
|
364
|
+
export async function readMintlifyMarkdown(rawUrl) {
|
|
365
|
+
const resolved = await resolveDocsLink(rawUrl);
|
|
366
|
+
if (resolved.outcome !== 'identity' && resolved.outcome !== 'redirect') {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
const pageUrl = new URL(resolved.url);
|
|
370
|
+
const response = await fetch(`${MINTLIFY_HOST}${pageUrl.pathname}.md`, {
|
|
371
|
+
headers: { accept: 'text/markdown' },
|
|
372
|
+
redirect: 'follow',
|
|
373
|
+
signal: AbortSignal.timeout(15000)
|
|
374
|
+
});
|
|
375
|
+
if (response.status === 404) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new Error(`Mintlify returned ${response.status} for ${pageUrl.pathname}.md`);
|
|
380
|
+
}
|
|
381
|
+
const flavour = toMintlifyCandidate(rawUrl)?.flavour ?? 'rest';
|
|
382
|
+
const cleaned = cleanMintlifyMarkdown((await response.text()).replace(INDEX_BANNER, ''), flavour);
|
|
383
|
+
// The page body still links other pages on dev.wix.com; resolve those too.
|
|
384
|
+
const { text: rewritten } = await rewriteDocsLinks(cleaned, {
|
|
385
|
+
label: `read ${pageUrl.pathname}`
|
|
386
|
+
});
|
|
387
|
+
const markdown = absolutizeSiteLinks(rewritten);
|
|
388
|
+
logger.log(`[mintlify] read ${pageUrl.pathname}.md (${markdown.length} chars) for ${rawUrl}`);
|
|
389
|
+
return { url: `${MINTLIFY_HOST}${pageUrl.pathname}`, markdown };
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=mintlify-docs.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DocsTool } from './docs.js';
|
|
2
2
|
export declare const DEFAULT_MAX_RESULTS = 15;
|
|
3
|
-
export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxResults, rerank, linesInEachResult, usePlatformizedSearch, extraDocumentTypes }: {
|
|
3
|
+
export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxResults, rerank, linesInEachResult, usePlatformizedSearch, extraDocumentTypes, documentTypes }: {
|
|
4
4
|
toolName: DocsTool;
|
|
5
5
|
toolParams: Record<string, string>;
|
|
6
6
|
maxResults?: number;
|
|
@@ -8,5 +8,6 @@ export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxRes
|
|
|
8
8
|
linesInEachResult?: number | undefined;
|
|
9
9
|
usePlatformizedSearch?: boolean;
|
|
10
10
|
extraDocumentTypes?: string[];
|
|
11
|
+
documentTypes?: string[];
|
|
11
12
|
}) => Promise<string>;
|
|
12
13
|
//# sourceMappingURL=semanticSearch.d.ts.map
|
|
@@ -46,6 +46,9 @@ const runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank =
|
|
|
46
46
|
else if (toolName === 'CLI') {
|
|
47
47
|
kbNames.push('CLI_KB_ID');
|
|
48
48
|
}
|
|
49
|
+
else if (toolName === 'HELP_CENTER') {
|
|
50
|
+
kbNames.push('HELP_CENTER_KB_ID');
|
|
51
|
+
}
|
|
49
52
|
logger.log(`[SemanticSearch] Tool: ${toolName}, KBs: [${kbNames.join(', ')}], Query: "${toolParams.searchTerm}"`);
|
|
50
53
|
for (const kbName of kbNames) {
|
|
51
54
|
url.searchParams.append('kbName', kbName);
|
|
@@ -70,13 +73,16 @@ const runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank =
|
|
|
70
73
|
logger.log('Ran RAG search successfully with params', JSON.stringify(toolParams), timeTaskFormatted, typeof data);
|
|
71
74
|
return data?.results;
|
|
72
75
|
};
|
|
73
|
-
const runPlatformizedSemanticSearch = async (toolName, searchTerm, maxResults = 20, linesInEachResult, extraDocumentTypes) => {
|
|
76
|
+
const runPlatformizedSemanticSearch = async (toolName, searchTerm, maxResults = 20, linesInEachResult, extraDocumentTypes, documentTypesOverride) => {
|
|
74
77
|
const startTime = Date.now();
|
|
75
78
|
// When extra types are requested, search them in one combined ranking via the
|
|
76
79
|
// plural `document_types` field (mutually exclusive with `document_type`).
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// An override replaces the tool's own types (the Mintlify KBs).
|
|
81
|
+
const documentTypes = documentTypesOverride?.length
|
|
82
|
+
? documentTypesOverride
|
|
83
|
+
: extraDocumentTypes?.length
|
|
84
|
+
? [toolName, ...extraDocumentTypes]
|
|
85
|
+
: undefined;
|
|
80
86
|
logger.log(`[SemanticSearch/v2] Tool: ${documentTypes ? documentTypes.join('+') : toolName}, Query: "${searchTerm}"`);
|
|
81
87
|
const response = await fetch('https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown', {
|
|
82
88
|
method: 'POST',
|
|
@@ -105,9 +111,10 @@ const runPlatformizedSemanticSearch = async (toolName, searchTerm, maxResults =
|
|
|
105
111
|
}
|
|
106
112
|
return content;
|
|
107
113
|
};
|
|
108
|
-
export const runSemanticSearchAndFormat = async ({ toolName, toolParams, maxResults = DEFAULT_MAX_RESULTS, rerank, linesInEachResult, usePlatformizedSearch = true, extraDocumentTypes }) => {
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
export const runSemanticSearchAndFormat = async ({ toolName, toolParams, maxResults = DEFAULT_MAX_RESULTS, rerank, linesInEachResult, usePlatformizedSearch = true, extraDocumentTypes, documentTypes }) => {
|
|
115
|
+
// A document-type override names platformized types; the legacy path can't honor it.
|
|
116
|
+
if (usePlatformizedSearch || documentTypes?.length) {
|
|
117
|
+
return runPlatformizedSemanticSearch(toolName, toolParams.searchTerm, maxResults, linesInEachResult, extraDocumentTypes, documentTypes);
|
|
111
118
|
}
|
|
112
119
|
else {
|
|
113
120
|
const results = await runSemanticSearch(toolName, toolParams, maxResults, rerank);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { WixMcpServer } from '../wix-mcp-server.js';
|
|
2
|
+
export declare const NO_PERSONAL_DATA = "No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
2
3
|
export declare const START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS: {
|
|
3
|
-
readonly userRequest: "
|
|
4
|
-
readonly siteDescription: "What the site is: its purpose and who it is for. One or two sentences.";
|
|
5
|
-
readonly siteForm: "The form the site exists in right now (HTML/CSS/JS files, a static build, a zip, another builder's export, a design created in this conversation, or something else), roughly how many files, and whether it includes images.";
|
|
6
|
-
readonly siteFunctionality: "What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so.";
|
|
7
|
-
readonly agentIdentity: "Which agent or product you are and the harness you run in.";
|
|
8
|
-
readonly wixAccess: "How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in;
|
|
4
|
+
readonly userRequest: "A short summary of what the user asked for and the context that led here, in a sentence or two, including whether this is a new site or an edit or new version of a site already on Wix. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
5
|
+
readonly siteDescription: "What the site is: its purpose and who it is for. One or two sentences. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
6
|
+
readonly siteForm: "The form the site exists in right now (HTML/CSS/JS files, a static build, a zip, a framework project, another builder's export, a design created in this conversation, or something else), roughly how many files, and whether it includes images. For a framework project: which framework, whether you have its built static output (for example dist/) or only the source, and whether it needs a server at runtime (SSR, API routes, a backend), or that you don't know. Also where it came from: built in this conversation, the user's own project or repo, another builder (which one), or a Wix skill or template (which one). No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
7
|
+
readonly siteFunctionality: "What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
8
|
+
readonly agentIdentity: "Which agent or product you are, which model if you know it, and the harness you run in. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
9
|
+
readonly wixAccess: "How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in; which Wix identity you act with — the user's login through this MCP, a CLI login, or none; and which Wix skills you have (for example wix-headless-kit or wix-manage), by name and where they come from (installed on disk, a plugin, read online), or none. Name the kind of identity, not the account or its email. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
9
10
|
};
|
|
10
11
|
export declare const START_DEPLOY_WEBSITE_PARAMS: Array<keyof typeof START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS>;
|
|
11
12
|
export declare const START_DEPLOY_WEBSITE_TOOL_NAME = "StartDeployWebsite";
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { defaultToolDescriptions } from '../config/default-config.js';
|
|
3
3
|
import { fetchArticleContent } from './fetch-article.js';
|
|
4
|
+
// Personal data in these fields is also redacted before they are recorded
|
|
5
|
+
// (redact-personal-data.ts); this line keeps it out in the first place.
|
|
6
|
+
export const NO_PERSONAL_DATA = 'No personal data: no names, emails, phones, passwords, keys or local paths.';
|
|
4
7
|
// These arguments are the only record of who deploys what; they are read by
|
|
5
8
|
// people via BI and do not change the response. One field per question so each
|
|
6
9
|
// can be analyzed on its own.
|
|
7
10
|
export const START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS = {
|
|
8
|
-
userRequest:
|
|
9
|
-
siteDescription:
|
|
10
|
-
siteForm:
|
|
11
|
-
siteFunctionality:
|
|
12
|
-
agentIdentity:
|
|
13
|
-
wixAccess:
|
|
11
|
+
userRequest: `A short summary of what the user asked for and the context that led here, in a sentence or two, including whether this is a new site or an edit or new version of a site already on Wix. ${NO_PERSONAL_DATA}`,
|
|
12
|
+
siteDescription: `What the site is: its purpose and who it is for. One or two sentences. ${NO_PERSONAL_DATA}`,
|
|
13
|
+
siteForm: `The form the site exists in right now (HTML/CSS/JS files, a static build, a zip, a framework project, another builder's export, a design created in this conversation, or something else), roughly how many files, and whether it includes images. For a framework project: which framework, whether you have its built static output (for example dist/) or only the source, and whether it needs a server at runtime (SSR, API routes, a backend), or that you don't know. Also where it came from: built in this conversation, the user's own project or repo, another builder (which one), or a Wix skill or template (which one). ${NO_PERSONAL_DATA}`,
|
|
14
|
+
siteFunctionality: `What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so. ${NO_PERSONAL_DATA}`,
|
|
15
|
+
agentIdentity: `Which agent or product you are, which model if you know it, and the harness you run in. ${NO_PERSONAL_DATA}`,
|
|
16
|
+
wixAccess: `How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in; which Wix identity you act with — the user's login through this MCP, a CLI login, or none; and which Wix skills you have (for example wix-headless-kit or wix-manage), by name and where they come from (installed on disk, a plugin, read online), or none. Name the kind of identity, not the account or its email. ${NO_PERSONAL_DATA}`
|
|
14
17
|
};
|
|
15
18
|
export const START_DEPLOY_WEBSITE_PARAMS = Object.keys(START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS);
|
|
19
|
+
// Named in SERVER_INSTRUCTIONS (config/default-config.ts): when this tool's name or what it is for changes, update them to match.
|
|
16
20
|
export const START_DEPLOY_WEBSITE_TOOL_NAME = 'StartDeployWebsite';
|
|
17
21
|
export const START_DEPLOY_WEBSITE_RECIPE_URL = 'https://dev.wix.com/docs/api-reference/account-level/sites/skills/upload-a-website-or-html-files.md';
|
|
18
22
|
export const DROP_PAGE_URL = 'https://www.wix.com/headless/drop?utm_campaign=mcp';
|
|
23
|
+
const WRITE_THE_CODE_LINE = "When the user brought no files, write the site's code yourself, then upload it as the guide shows.";
|
|
19
24
|
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.`;
|
|
20
25
|
// The tool exists to take the agent's attention at routing time; the work itself
|
|
21
26
|
// is the recipe, so the response IS the recipe — fetched live and returned whole,
|
|
22
27
|
// so the agent never has to decide whether to go read it.
|
|
23
28
|
export function startDeployWebsiteResponse(recipe) {
|
|
24
29
|
return [
|
|
25
|
-
`The following is the content of ${START_DEPLOY_WEBSITE_RECIPE_URL}. Follow it end to end and do the publishing yourself — do not hand the user instructions
|
|
30
|
+
`The following is the content of ${START_DEPLOY_WEBSITE_RECIPE_URL}. Follow it end to end and do the publishing yourself — do not hand the user instructions. ${WRITE_THE_CODE_LINE}`,
|
|
26
31
|
FALLBACK_LINE,
|
|
27
32
|
'---',
|
|
28
33
|
recipe.trim()
|
|
@@ -31,7 +36,7 @@ export function startDeployWebsiteResponse(recipe) {
|
|
|
31
36
|
// When the recipe can't be fetched, the pointer still routes correctly.
|
|
32
37
|
export const START_DEPLOY_WEBSITE_POINTER_RESPONSE = [
|
|
33
38
|
`Read and follow this recipe end to end: ${START_DEPLOY_WEBSITE_RECIPE_URL}`,
|
|
34
|
-
|
|
39
|
+
`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. ${WRITE_THE_CODE_LINE}`,
|
|
35
40
|
FALLBACK_LINE
|
|
36
41
|
].join('\n\n');
|
|
37
42
|
export function addStartDeployWebsiteTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
|
|
@@ -3,17 +3,15 @@ import { type StartDeployWebsiteFetchOptions } from './start-deploy-website.js';
|
|
|
3
3
|
export declare const START_HEADLESS_BUILD_TOOL_NAME = "StartHeadlessBuild";
|
|
4
4
|
/** Wix's headless guide; identical to https://www.wix.com/headless/skill.md. */
|
|
5
5
|
export declare const HEADLESS_GUIDE_URL = "https://www.wix.com/skills/headless-cold-start/headless-kit.md";
|
|
6
|
-
export declare const START_HEADLESS_BUILD_STARTING_POINTS: readonly ["nothing yet", "a design worked out in this conversation", "an existing Wix Headless site", "a project download link", "a Wix site to move to code"];
|
|
7
|
-
type StartingPoint = (typeof START_HEADLESS_BUILD_STARTING_POINTS)[number];
|
|
8
6
|
export declare const START_HEADLESS_BUILD_PARAM_DESCRIPTIONS: {
|
|
9
|
-
readonly userRequest: "
|
|
10
|
-
readonly siteDescription: "What the site is: its purpose and who it is for. One or two sentences.";
|
|
11
|
-
readonly startingPoint: "What the
|
|
12
|
-
readonly siteFunctionality: "What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so.";
|
|
13
|
-
readonly agentIdentity: "Which agent or product you are and the harness you run in.";
|
|
14
|
-
readonly wixAccess: "How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in;
|
|
7
|
+
readonly userRequest: "A short summary of what the user asked for and the context that led here, in a sentence or two, including whether this is a new site or an edit or new version of a site already on Wix. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
8
|
+
readonly siteDescription: "What the site is: its purpose and who it is for. One or two sentences. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
9
|
+
readonly startingPoint: "What the site starts from, in your own words: a new site; an existing Wix Headless site (a site deployed from files is one), with its metaSiteId if you have it; a project download link (a download.zip from Wix); a Wix site moving to code; or something else. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
10
|
+
readonly siteFunctionality: "What the site must be able to do for its visitors beyond showing pages, if anything (for example selling, bookings or reservations, tickets, menus and orders, rentals, forms, memberships or paid plans, donations, a blog, or anything else the user expects to work), in your own words. If it is a plain static site with no such needs, say so. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
11
|
+
readonly agentIdentity: "Which agent or product you are, which model if you know it, and the harness you run in. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
12
|
+
readonly wixAccess: "How you can reach Wix from where you run: through this MCP only, or also from a shell whose network can reach wixapis.com; whether you can run the Wix CLI (npx @wix/cli) there and whether it is logged in; which Wix identity you act with — the user's login through this MCP, a CLI login, or none; and which Wix skills you have (for example wix-headless-kit or wix-manage), by name and where they come from (installed on disk, a plugin, read online), or none. Name the kind of identity, not the account or its email. No personal data: no names, emails, phones, passwords, keys or local paths.";
|
|
15
13
|
};
|
|
16
|
-
|
|
14
|
+
/** The same response for every call: the headless guide, inline. */
|
|
15
|
+
export declare function startHeadlessBuildResponse(guide: string | undefined): string;
|
|
17
16
|
export declare function addStartHeadlessBuildTool(server: WixMcpServer, disableTools?: string[], toolDescriptions?: Record<string, string>, fetchOptions?: StartDeployWebsiteFetchOptions): void;
|
|
18
|
-
export {};
|
|
19
17
|
//# sourceMappingURL=start-headless-build.d.ts.map
|
|
@@ -1,41 +1,29 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { defaultToolDescriptions } from '../config/default-config.js';
|
|
3
3
|
import { fetchArticleContent } from './fetch-article.js';
|
|
4
|
-
import { START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS } from './start-deploy-website.js';
|
|
4
|
+
import { NO_PERSONAL_DATA, START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS } from './start-deploy-website.js';
|
|
5
|
+
// Named in SERVER_INSTRUCTIONS (config/default-config.ts): when this tool's name or what it is for changes, update them to match.
|
|
5
6
|
export const START_HEADLESS_BUILD_TOOL_NAME = 'StartHeadlessBuild';
|
|
6
7
|
/** Wix's headless guide; identical to https://www.wix.com/headless/skill.md. */
|
|
7
8
|
export const HEADLESS_GUIDE_URL = 'https://www.wix.com/skills/headless-cold-start/headless-kit.md';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'a design worked out in this conversation',
|
|
11
|
-
'an existing Wix Headless site',
|
|
12
|
-
'a project download link',
|
|
13
|
-
'a Wix site to move to code'
|
|
14
|
-
];
|
|
15
|
-
// Like StartDeployWebsite's, these arguments are read by people via BI; only
|
|
16
|
-
// `startingPoint` changes the response.
|
|
9
|
+
// Like StartDeployWebsite's, these arguments are read by people via BI and do not change the
|
|
10
|
+
// response.
|
|
17
11
|
export const START_HEADLESS_BUILD_PARAM_DESCRIPTIONS = {
|
|
18
12
|
userRequest: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.userRequest,
|
|
19
13
|
siteDescription: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteDescription,
|
|
20
|
-
startingPoint:
|
|
14
|
+
startingPoint: `What the site starts from, in your own words: a new site; an existing Wix Headless site (a site deployed from files is one), with its metaSiteId if you have it; a project download link (a download.zip from Wix); a Wix site moving to code; or something else. ${NO_PERSONAL_DATA}`,
|
|
21
15
|
siteFunctionality: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.siteFunctionality,
|
|
22
16
|
agentIdentity: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.agentIdentity,
|
|
23
17
|
wixAccess: START_DEPLOY_WEBSITE_PARAM_DESCRIPTIONS.wixAccess
|
|
24
18
|
};
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (startingPoint === 'a Wix site to move to code') {
|
|
30
|
-
lines.push(MOVE_TO_CODE_LINE);
|
|
31
|
-
}
|
|
19
|
+
const BUILT_HERE = "The site is built here, in code: don't offer Wix's editor or a template instead.";
|
|
20
|
+
const PREFIX = `The following is Wix's headless guide. Follow it end to end and do the work yourself: you write the site, and the guide creates it, connects its Wix business features, and uploads it. ${BUILT_HERE}`;
|
|
21
|
+
/** The same response for every call: the headless guide, inline. */
|
|
22
|
+
export function startHeadlessBuildResponse(guide) {
|
|
32
23
|
if (guide && guide.trim()) {
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
lines.push(`Wix's headless guide could not be loaded here. It is at ${HEADLESS_GUIDE_URL}: read it whole with ReadFullDocsArticle if you have that tool, otherwise fetch it with curl or your web tool, then follow it end to end and do the work yourself. If you find the user already has the site's code, call StartDeployWebsite instead.`);
|
|
24
|
+
return [PREFIX, '---', guide.trim()].join('\n\n');
|
|
37
25
|
}
|
|
38
|
-
return
|
|
26
|
+
return `Wix's headless guide could not be loaded here. It is at ${HEADLESS_GUIDE_URL}: read it whole with ReadFullDocsArticle if you have that tool, otherwise fetch it with curl or your web tool, then follow it end to end and do the work yourself. ${BUILT_HERE}`;
|
|
39
27
|
}
|
|
40
28
|
export function addStartHeadlessBuildTool(server, disableTools = [], toolDescriptions, fetchOptions = {}) {
|
|
41
29
|
if (disableTools.includes(START_HEADLESS_BUILD_TOOL_NAME)) {
|
|
@@ -46,13 +34,11 @@ export function addStartHeadlessBuildTool(server, disableTools = [], toolDescrip
|
|
|
46
34
|
defaultToolDescriptions[START_HEADLESS_BUILD_TOOL_NAME], {
|
|
47
35
|
userRequest: z.string().min(1).describe(d.userRequest),
|
|
48
36
|
siteDescription: z.string().min(1).describe(d.siteDescription),
|
|
49
|
-
startingPoint: z
|
|
50
|
-
.enum(START_HEADLESS_BUILD_STARTING_POINTS)
|
|
51
|
-
.describe(d.startingPoint),
|
|
37
|
+
startingPoint: z.string().optional().describe(d.startingPoint),
|
|
52
38
|
siteFunctionality: z.string().min(1).describe(d.siteFunctionality),
|
|
53
39
|
agentIdentity: z.string().min(1).describe(d.agentIdentity),
|
|
54
40
|
wixAccess: z.string().min(1).describe(d.wixAccess)
|
|
55
|
-
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (
|
|
41
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (_args, { httpClient }) => {
|
|
56
42
|
// Inline, not a link: chat agents cannot fetch www.wix.com themselves.
|
|
57
43
|
let guide;
|
|
58
44
|
try {
|
|
@@ -65,7 +51,7 @@ export function addStartHeadlessBuildTool(server, disableTools = [], toolDescrip
|
|
|
65
51
|
content: [
|
|
66
52
|
{
|
|
67
53
|
type: 'text',
|
|
68
|
-
text: startHeadlessBuildResponse(guide
|
|
54
|
+
text: startHeadlessBuildResponse(guide)
|
|
69
55
|
}
|
|
70
56
|
]
|
|
71
57
|
};
|