@wix/mcp 1.0.82 → 1.0.83

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.
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Introspection client for the Wix GraphQL schema.
3
+ *
4
+ * The full schema is ~4MB (about 4,100 types), far too large to hand to an
5
+ * agent, so the tools disclose it progressively: the root operations (queries
6
+ * and mutations with their descriptions) are fetched once and cached
7
+ * in-process for ranking, and individual types are fetched on demand by name.
8
+ * Introspection needs no auth, so this works for anonymous sessions too.
9
+ */
10
+ import { WIX_GRAPHQL_ENDPOINT } from './constants.js';
11
+ const TYPE_REF_FRAGMENT = `
12
+ fragment TR on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }`;
13
+ const ROOT_OPERATIONS_QUERY = `{
14
+ __schema {
15
+ queryType { fields { name description args { name type { ...TR } } type { ...TR } } }
16
+ mutationType { fields { name description args { name type { ...TR } } type { ...TR } } }
17
+ }
18
+ }${TYPE_REF_FRAGMENT}`;
19
+ const TYPE_DETAILS_FRAGMENT = `
20
+ fragment TD on __Type {
21
+ kind name description
22
+ fields { name description type { ...TR } args { name type { ...TR } } }
23
+ inputFields { name description type { ...TR } defaultValue }
24
+ enumValues { name description }
25
+ possibleTypes { name }
26
+ }`;
27
+ const ROOT_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
28
+ let rootCache;
29
+ const typeCache = new Map();
30
+ /** Test hook: forget every cached introspection result. */
31
+ export function resetGraphqlSchemaCache() {
32
+ rootCache = undefined;
33
+ typeCache.clear();
34
+ }
35
+ /**
36
+ * The endpoint answers bursts with transient 503s (an HTML page). Retry a few
37
+ * times with backoff; introspection is idempotent so this is always safe.
38
+ */
39
+ export const RETRY_DELAYS_MS = [800, 2000, 4000];
40
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
41
+ export async function fetchWithRetry(fetchImpl, url, init, delays = RETRY_DELAYS_MS) {
42
+ let attempt = 0;
43
+ for (;;) {
44
+ const resp = await fetchImpl(url, init);
45
+ if (resp.status < 500 || attempt >= delays.length)
46
+ return resp;
47
+ await sleep(delays[attempt++]);
48
+ }
49
+ }
50
+ async function postIntrospection(query, options) {
51
+ const fetchImpl = options.fetchImpl ?? fetch;
52
+ const endpoint = options.endpoint ?? WIX_GRAPHQL_ENDPOINT;
53
+ const resp = await fetchWithRetry(fetchImpl, endpoint, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ Accept: 'application/json'
58
+ },
59
+ body: JSON.stringify({ query })
60
+ }, options.retryDelaysMs);
61
+ const text = await resp.text();
62
+ if (!resp.ok) {
63
+ const body = /^\s*<(!doctype|html)/i.test(text)
64
+ ? '(HTML error page — the endpoint is temporarily unavailable; try again in a moment)'
65
+ : text.slice(0, 500);
66
+ throw new Error(`GraphQL introspection failed (${resp.status} ${resp.statusText}): ${body}`);
67
+ }
68
+ const json = JSON.parse(text);
69
+ if (json.errors?.length && !json.data) {
70
+ throw new Error(`GraphQL introspection returned errors: ${JSON.stringify(json.errors).slice(0, 500)}`);
71
+ }
72
+ return json.data;
73
+ }
74
+ /** All root query + mutation fields, cached in-process. */
75
+ export function getRootOperations(options = {}) {
76
+ const now = Date.now();
77
+ if (rootCache && now - rootCache.fetchedAt < ROOT_CACHE_TTL_MS) {
78
+ return rootCache.promise;
79
+ }
80
+ const promise = postIntrospection(ROOT_OPERATIONS_QUERY, options).then((data) => {
81
+ const toOps = (fields, kind) => (fields ?? []).map((f) => ({
82
+ kind,
83
+ name: f.name,
84
+ description: f.description ?? '',
85
+ args: f.args ?? [],
86
+ type: f.type
87
+ }));
88
+ return [
89
+ ...toOps(data?.__schema?.queryType?.fields, 'query'),
90
+ ...toOps(data?.__schema?.mutationType?.fields, 'mutation')
91
+ ];
92
+ });
93
+ // Don't pin a failed fetch for the TTL.
94
+ promise.catch(() => {
95
+ if (rootCache?.promise === promise)
96
+ rootCache = undefined;
97
+ });
98
+ rootCache = { promise, fetchedAt: now };
99
+ return promise;
100
+ }
101
+ const GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
102
+ /** Aliases per introspection document — the server rejects more than 15. */
103
+ const TYPE_BATCH_SIZE = 10;
104
+ /**
105
+ * Fetch several named types in one round-trip (aliased `__type` lookups).
106
+ * Unknown names resolve to null. Results are cached per name.
107
+ */
108
+ export async function getTypes(names, options = {}) {
109
+ const unique = [...new Set(names)].filter((n) => GRAPHQL_NAME.test(n));
110
+ const missing = unique.filter((n) => !typeCache.has(n));
111
+ // The endpoint caps aliases per document (15) and answers bursts with
112
+ // 503s, so look types up in small chunks, one chunk at a time.
113
+ for (let start = 0; start < missing.length; start += TYPE_BATCH_SIZE) {
114
+ const chunk = missing.slice(start, start + TYPE_BATCH_SIZE);
115
+ const selections = chunk
116
+ .map((n, i) => `t${i}: __type(name: "${n}") { ...TD }`)
117
+ .join('\n');
118
+ const query = `{\n${selections}\n}${TYPE_DETAILS_FRAGMENT}${TYPE_REF_FRAGMENT}`;
119
+ const batch = postIntrospection(query, options);
120
+ chunk.forEach((n, i) => {
121
+ const p = batch.then((data) => (data?.[`t${i}`] ?? null));
122
+ // A failed batch must not poison the cache for later calls.
123
+ p.catch(() => {
124
+ if (typeCache.get(n) === p)
125
+ typeCache.delete(n);
126
+ });
127
+ typeCache.set(n, p);
128
+ });
129
+ // Sequential on purpose (see above); a failure surfaces via the cached
130
+ // promise when the caller awaits it.
131
+ await batch.catch(() => undefined);
132
+ }
133
+ const result = new Map();
134
+ await Promise.all(unique.map(async (n) => {
135
+ result.set(n, await typeCache.get(n));
136
+ }));
137
+ return result;
138
+ }
139
+ /** `[Foo!]!` style rendering of a (possibly wrapped) type ref. */
140
+ export function renderTypeRef(ref) {
141
+ if (!ref)
142
+ return 'Unknown';
143
+ if (ref.kind === 'NON_NULL')
144
+ return `${renderTypeRef(ref.ofType)}!`;
145
+ if (ref.kind === 'LIST')
146
+ return `[${renderTypeRef(ref.ofType)}]`;
147
+ return ref.name ?? 'Unknown';
148
+ }
149
+ /** The named type at the bottom of LIST/NON_NULL wrappers. */
150
+ export function namedType(ref) {
151
+ let cur = ref;
152
+ while (cur && (cur.kind === 'NON_NULL' || cur.kind === 'LIST')) {
153
+ cur = cur.ofType ?? null;
154
+ }
155
+ return cur?.name ?? null;
156
+ }
157
+ const BUILTIN_SCALARS = new Set([
158
+ 'String',
159
+ 'Int',
160
+ 'Float',
161
+ 'Boolean',
162
+ 'ID',
163
+ 'JSON'
164
+ ]);
165
+ export function isBuiltinScalar(name) {
166
+ return !!name && BUILTIN_SCALARS.has(name);
167
+ }
168
+ function oneLine(text, max) {
169
+ if (!text)
170
+ return '';
171
+ const flat = text.replace(/\s+/g, ' ').trim();
172
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
173
+ }
174
+ function renderField(field, isInput) {
175
+ const args = !isInput && field.args?.length
176
+ ? `(${field.args.map((a) => `${a.name}: ${renderTypeRef(a.type)}`).join(', ')})`
177
+ : '';
178
+ const def = isInput && field.defaultValue != null ? ` = ${field.defaultValue}` : '';
179
+ const comment = field.description
180
+ ? ` # ${oneLine(field.description, 140)}`
181
+ : '';
182
+ return ` ${field.name}${args}: ${renderTypeRef(field.type)}${def}${comment}`;
183
+ }
184
+ /**
185
+ * Compact SDL for one type. Returns the text and the named types it references
186
+ * (so callers can expand the next level).
187
+ */
188
+ export function renderType(type) {
189
+ const references = new Set();
190
+ const collect = (ref) => {
191
+ const n = namedType(ref);
192
+ if (n && !isBuiltinScalar(n) && n !== type.name)
193
+ references.add(n);
194
+ };
195
+ const header = type.description
196
+ ? `# ${oneLine(type.description, 200)}\n`
197
+ : '';
198
+ let body;
199
+ switch (type.kind) {
200
+ case 'ENUM':
201
+ body = `enum ${type.name} {\n${(type.enumValues ?? [])
202
+ .map((v) => ` ${v.name}${v.description ? ` # ${oneLine(v.description, 100)}` : ''}`)
203
+ .join('\n')}\n}`;
204
+ break;
205
+ case 'INPUT_OBJECT':
206
+ (type.inputFields ?? []).forEach((f) => collect(f.type));
207
+ body = `input ${type.name} {\n${(type.inputFields ?? [])
208
+ .map((f) => renderField(f, true))
209
+ .join('\n')}\n}`;
210
+ break;
211
+ case 'UNION':
212
+ (type.possibleTypes ?? []).forEach((p) => references.add(p.name));
213
+ body = `union ${type.name} = ${(type.possibleTypes ?? []).map((p) => p.name).join(' | ')}`;
214
+ break;
215
+ case 'SCALAR':
216
+ body = `scalar ${type.name}`;
217
+ break;
218
+ default: {
219
+ (type.fields ?? []).forEach((f) => {
220
+ collect(f.type);
221
+ f.args?.forEach((a) => collect(a.type));
222
+ });
223
+ const keyword = type.kind === 'INTERFACE' ? 'interface' : 'type';
224
+ body = `${keyword} ${type.name} {\n${(type.fields ?? [])
225
+ .map((f) => renderField(f, false))
226
+ .join('\n')}\n}`;
227
+ }
228
+ }
229
+ return { text: header + body, references: [...references] };
230
+ }
231
+ /** One-line signature of a root operation. */
232
+ export function renderOperationSignature(op) {
233
+ const args = op.args
234
+ .map((a) => `${a.name}: ${renderTypeRef(a.type)}`)
235
+ .join(', ');
236
+ return `${op.kind === 'query' ? 'Query' : 'Mutation'}.${op.name}(${args}): ${renderTypeRef(op.type)}`;
237
+ }
238
+ /**
239
+ * Breadth-first expansion of `rootTypeNames`. Level 1 is the given names,
240
+ * level 2 the types they reference, and so on up to `depth`. Anything
241
+ * referenced but not expanded is returned as `unexpanded` for a follow-up.
242
+ */
243
+ export async function expandTypes(rootTypeNames, options = {}) {
244
+ const depth = Math.max(1, Math.min(options.depth ?? 2, 5));
245
+ const maxTypes = options.maxTypes ?? 40;
246
+ const rendered = [];
247
+ const unknown = [];
248
+ const seen = new Set();
249
+ let frontier = [...new Set(rootTypeNames)].filter((n) => !isBuiltinScalar(n));
250
+ let level = 1;
251
+ let overflow = [];
252
+ while (frontier.length > 0 && level <= depth) {
253
+ const room = maxTypes - seen.size;
254
+ if (room <= 0) {
255
+ overflow = frontier;
256
+ break;
257
+ }
258
+ const batch = frontier.slice(0, room);
259
+ overflow = frontier.slice(room);
260
+ batch.forEach((n) => seen.add(n));
261
+ const types = await getTypes(batch, options);
262
+ const next = new Set();
263
+ for (const name of batch) {
264
+ const type = types.get(name);
265
+ if (!type) {
266
+ unknown.push(name);
267
+ continue;
268
+ }
269
+ const { text, references } = renderType(type);
270
+ rendered.push(text);
271
+ references.forEach((r) => {
272
+ if (!seen.has(r))
273
+ next.add(r);
274
+ });
275
+ }
276
+ frontier = [...next];
277
+ level++;
278
+ }
279
+ const unexpanded = [...new Set([...overflow, ...frontier])].filter((n) => !seen.has(n) && !isBuiltinScalar(n));
280
+ return { rendered, unexpanded, unknown };
281
+ }
282
+ //# sourceMappingURL=introspection.js.map
@@ -0,0 +1,25 @@
1
+ import { type RootOperation } from './introspection.js';
2
+ export interface OperationMatch {
3
+ operation: string;
4
+ kind: 'query' | 'mutation';
5
+ /** `name(arg: Type): ReturnType` */
6
+ signature: string;
7
+ description: string;
8
+ inputType: string | null;
9
+ returnType: string | null;
10
+ score: number;
11
+ }
12
+ /** Split camelCase / digits / punctuation into lowercase tokens. */
13
+ export declare function tokenize(text: string): string[];
14
+ /** Crude singularisation so "products" matches "product" and vice versa. */
15
+ export declare function stem(token: string): string;
16
+ /**
17
+ * Rank root operations for a free-text intent. Name-token matches weigh most
18
+ * (the operation name encodes app, service, version, verb and entity), then
19
+ * description matches; exact full-name matches win outright.
20
+ */
21
+ export declare function searchOperations(ops: RootOperation[], query: string, options?: {
22
+ kind?: 'query' | 'mutation' | 'all';
23
+ maxResults?: number;
24
+ }): OperationMatch[];
25
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1,277 @@
1
+ import { namedType, renderTypeRef } from './introspection.js';
2
+ /** Split camelCase / digits / punctuation into lowercase tokens. */
3
+ export function tokenize(text) {
4
+ return text
5
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
6
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
7
+ .replace(/([a-zA-Z])([0-9])/g, '$1 $2')
8
+ .replace(/([0-9])([a-zA-Z])/g, '$1 $2')
9
+ .toLowerCase()
10
+ .split(/[^a-z0-9]+/)
11
+ .filter((t) => t.length > 0);
12
+ }
13
+ const STOP_WORDS = new Set([
14
+ 'a',
15
+ 'an',
16
+ 'the',
17
+ 'of',
18
+ 'for',
19
+ 'to',
20
+ 'in',
21
+ 'on',
22
+ 'with',
23
+ 'and',
24
+ 'or',
25
+ 'by',
26
+ 'all',
27
+ 'my',
28
+ 'me',
29
+ 'i',
30
+ 'from',
31
+ 'is',
32
+ 'are',
33
+ 'be',
34
+ 'it',
35
+ 'this',
36
+ 'that',
37
+ 'wix',
38
+ 'api',
39
+ 'graphql'
40
+ ]);
41
+ /** Crude singularisation so "products" matches "product" and vice versa. */
42
+ export function stem(token) {
43
+ if (token.length > 4 && token.endsWith('ies'))
44
+ return `${token.slice(0, -3)}y`;
45
+ if (token.length > 3 && token.endsWith('es') && !token.endsWith('ses'))
46
+ return token.slice(0, -1);
47
+ if (token.length > 3 && token.endsWith('s') && !token.endsWith('ss'))
48
+ return token.slice(0, -1);
49
+ return token;
50
+ }
51
+ /** Common intent verbs → the verbs Wix operation names use. */
52
+ const VERB_SYNONYMS = {
53
+ list: ['query', 'list', 'search'],
54
+ find: ['query', 'search', 'get'],
55
+ fetch: ['get', 'query'],
56
+ read: ['get', 'query'],
57
+ show: ['get', 'query'],
58
+ count: ['count', 'query'],
59
+ add: ['create', 'add'],
60
+ new: ['create'],
61
+ insert: ['create'],
62
+ edit: ['update'],
63
+ change: ['update'],
64
+ modify: ['update'],
65
+ set: ['update'],
66
+ remove: ['delete', 'remove'],
67
+ destroy: ['delete'],
68
+ cancel: ['cancel', 'delete'],
69
+ customer: ['contact', 'member'],
70
+ client: ['contact', 'member'],
71
+ item: ['product', 'item'],
72
+ inventory: ['inventory', 'stock'],
73
+ stock: ['inventory', 'stock'],
74
+ post: ['post', 'blog'],
75
+ article: ['post', 'blog'],
76
+ appointment: ['booking', 'session'],
77
+ collection: ['collection', 'category', 'data'],
78
+ cms: ['data', 'collection'],
79
+ store: ['stores', 'store']
80
+ };
81
+ function expandQueryTokens(tokens) {
82
+ const out = new Set();
83
+ for (const raw of tokens) {
84
+ if (STOP_WORDS.has(raw))
85
+ continue;
86
+ const s = stem(raw);
87
+ out.add(s);
88
+ for (const syn of VERB_SYNONYMS[raw] ?? VERB_SYNONYMS[s] ?? []) {
89
+ out.add(stem(syn));
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+ const READ_VERBS = new Set([
95
+ 'list',
96
+ 'get',
97
+ 'query',
98
+ 'find',
99
+ 'fetch',
100
+ 'read',
101
+ 'show',
102
+ 'retrieve',
103
+ 'search',
104
+ 'count',
105
+ 'view',
106
+ 'see'
107
+ ]);
108
+ const WRITE_VERBS = new Set([
109
+ 'create',
110
+ 'add',
111
+ 'new',
112
+ 'insert',
113
+ 'update',
114
+ 'edit',
115
+ 'change',
116
+ 'modify',
117
+ 'set',
118
+ 'delete',
119
+ 'remove',
120
+ 'destroy',
121
+ 'cancel',
122
+ 'bulk',
123
+ 'merge',
124
+ 'publish',
125
+ 'send'
126
+ ]);
127
+ /** Version tokens ("v", "3") carry no intent and are never penalised. */
128
+ const isVersionToken = (t) => t === 'v' || /^\d+$/.test(t);
129
+ /** Markdown links → their text, so descriptions read as plain prose. */
130
+ function stripMarkdown(text) {
131
+ return text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/[`*_]/g, '');
132
+ }
133
+ function firstSentence(text, max = 220) {
134
+ const flat = stripMarkdown(text).replace(/\s+/g, ' ').trim();
135
+ const cut = flat.search(/(?<=[.!?])\s/);
136
+ const sentence = cut > 0 ? flat.slice(0, cut) : flat;
137
+ return sentence.length > max ? `${sentence.slice(0, max - 1)}…` : sentence;
138
+ }
139
+ const indexCache = new WeakMap();
140
+ function indexOperations(ops) {
141
+ let indexed = indexCache.get(ops);
142
+ if (!indexed) {
143
+ indexed = ops.map((op) => ({
144
+ op,
145
+ nameTokens: new Set(tokenize(op.name).map(stem)),
146
+ descTokens: new Set(tokenize(op.description.slice(0, 600)).map(stem))
147
+ }));
148
+ indexCache.set(ops, indexed);
149
+ }
150
+ return indexed;
151
+ }
152
+ /**
153
+ * Rank root operations for a free-text intent. Name-token matches weigh most
154
+ * (the operation name encodes app, service, version, verb and entity), then
155
+ * description matches; exact full-name matches win outright.
156
+ */
157
+ export function searchOperations(ops, query, options = {}) {
158
+ const { kind = 'all', maxResults = 10 } = options;
159
+ const rawTokens = tokenize(query);
160
+ const terms = expandQueryTokens(rawTokens);
161
+ const originalTerms = new Set(rawTokens.filter((t) => !STOP_WORDS.has(t)).map(stem));
162
+ const exactName = query.trim();
163
+ const hasWriteVerb = rawTokens.some((t) => WRITE_VERBS.has(t));
164
+ // "contacts" wants the list root, "contact" the single-entity root.
165
+ const nounTokens = rawTokens.filter((t) => !STOP_WORDS.has(t) && !READ_VERBS.has(t) && !WRITE_VERBS.has(t));
166
+ const pluralIntent = nounTokens.length > 0 && nounTokens.some((t) => stem(t) !== t);
167
+ // A read intent (or no verb at all) prefers query roots: in this schema a
168
+ // query root IS the read verb, so "list/get/find" are satisfied by the kind.
169
+ const readIntent = !hasWriteVerb;
170
+ const matches = [];
171
+ for (const { op, nameTokens, descTokens } of indexOperations(ops)) {
172
+ if (kind !== 'all' && op.kind !== kind)
173
+ continue;
174
+ let score = 0;
175
+ let originalHits = 0;
176
+ const matchedNameTokens = new Set();
177
+ for (const term of terms) {
178
+ const isOriginal = originalTerms.has(term);
179
+ if (op.kind === 'query' && readIntent && READ_VERBS.has(term)) {
180
+ // The query root itself is the "list/get" — count the verb as matched.
181
+ score += isOriginal ? 4 : 2;
182
+ if (isOriginal)
183
+ originalHits++;
184
+ if (nameTokens.has(term))
185
+ matchedNameTokens.add(term);
186
+ continue;
187
+ }
188
+ if (nameTokens.has(term)) {
189
+ matchedNameTokens.add(term);
190
+ score += isOriginal ? 4 : 2;
191
+ if (isOriginal)
192
+ originalHits++;
193
+ continue;
194
+ }
195
+ let prefixHit = false;
196
+ for (const nt of nameTokens) {
197
+ if (term.length >= 3 && (nt.startsWith(term) || term.startsWith(nt))) {
198
+ prefixHit = true;
199
+ matchedNameTokens.add(nt);
200
+ break;
201
+ }
202
+ }
203
+ if (prefixHit) {
204
+ score += isOriginal ? 2 : 1;
205
+ if (isOriginal)
206
+ originalHits++;
207
+ continue;
208
+ }
209
+ if (descTokens.has(term)) {
210
+ score += 1;
211
+ if (isOriginal)
212
+ originalHits++;
213
+ }
214
+ }
215
+ // Nothing matched: biases below must not surface unrelated operations.
216
+ if (score <= 0 && op.name !== exactName)
217
+ continue;
218
+ if (op.name === exactName)
219
+ score += 100;
220
+ if (originalTerms.size > 0 && originalHits === originalTerms.size) {
221
+ score += 3;
222
+ }
223
+ // Plural intent ↔ list root (plural last token); singular ↔ get-one root.
224
+ const lastToken = tokenize(op.name)
225
+ .filter((t) => !isVersionToken(t))
226
+ .pop();
227
+ if (lastToken && nounTokens.length > 0) {
228
+ const pluralOp = stem(lastToken) !== lastToken;
229
+ score += pluralOp === pluralIntent ? 0.5 : -0.5;
230
+ }
231
+ // Deprecated/internal duplicates ("DoNotCall") rank below their twins.
232
+ if (/DoNotCall/i.test(op.name))
233
+ score -= 2;
234
+ // Name tokens the intent never mentioned dilute the match (a "Facets" or
235
+ // "Labels" op is not what "contacts" asked for). App prefixes count too,
236
+ // equally for every op, so they don't reorder siblings.
237
+ for (const nt of nameTokens) {
238
+ if (!isVersionToken(nt) && !matchedNameTokens.has(nt))
239
+ score -= 0.3;
240
+ }
241
+ // A read intent almost never wants a create/update/delete mutation even
242
+ // when its name shares the nouns ("products with inventory" is not
243
+ // CreateProductWithInventory).
244
+ if (readIntent &&
245
+ op.kind === 'mutation' &&
246
+ [...nameTokens].some((t) => WRITE_VERBS.has(t))) {
247
+ score -= 6;
248
+ }
249
+ // Read intents lean to query roots; newer API versions and documented
250
+ // operations edge out older/undocumented twins with the same tokens.
251
+ if (op.kind === 'query' && readIntent)
252
+ score += 1;
253
+ const version = op.name.match(/V(\d+)/);
254
+ if (version)
255
+ score += Math.min(Number(version[1]), 5) * 0.1;
256
+ if (!op.description)
257
+ score -= 0.5;
258
+ // Shorter names are the canonical entry for a family of similar ops.
259
+ score -= op.name.length / 200;
260
+ if (score <= 0)
261
+ continue;
262
+ matches.push({
263
+ operation: op.name,
264
+ kind: op.kind,
265
+ signature: `${op.name}(${op.args
266
+ .map((a) => `${a.name}: ${renderTypeRef(a.type)}`)
267
+ .join(', ')}): ${renderTypeRef(op.type)}`,
268
+ description: firstSentence(op.description),
269
+ inputType: op.args[0] ? namedType(op.args[0].type) : null,
270
+ returnType: namedType(op.type),
271
+ score: Math.round(score * 100) / 100
272
+ });
273
+ }
274
+ matches.sort((a, b) => b.score - a.score || a.operation.localeCompare(b.operation));
275
+ return matches.slice(0, Math.max(1, Math.min(maxResults, 50)));
276
+ }
277
+ //# sourceMappingURL=search.js.map
@@ -6,7 +6,7 @@ export { handleWixAPIResponse } from './tool-utils.js';
6
6
  export { addDocsResources } from './resources/docs.js';
7
7
  export { addSupportTool } from './support/index.js';
8
8
  export { addGetProfileTool } from './account-tools/get-profile-tool.js';
9
- export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool, type SiteWidgetToolOptions } from './site-widget-tools/index.js';
9
+ export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSuggestedDomainTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool, type SiteWidgetToolOptions } from './site-widget-tools/index.js';
10
10
  export * from './wixel/index.js';
11
11
  export { addImportClaudeDesignTool, type ImportClaudeDesignToolOptions } from './import-claude-design-tool/index.js';
12
12
  export { IMPORT_CLAUDE_DESIGN_TOOL_NAME } from './import-claude-design-tool/constants.js';
@@ -23,6 +23,7 @@ export { fetchRecipesIndex, fetchArticle, RECIPES_INDEX_URL } from './docs/fetch
23
23
  export { generateDocsNavigationTree, fetchMenuContent, MENU_CONTENT_THRESHOLD, normalizeMenuContent, summarizeLargeMenu, parseDirectChildren } from './docs/normalize-menu-content.js';
24
24
  export { addCodeModeTools, addExecuteCodeModeTool, addSearchCodeModeTool, type CodeModeOptions } from './code-mode/index.js';
25
25
  export { validateExecuteCode, EXECUTE_FUNCTION_EXPRESSION_HINT, type CodeValidationResult } from './code-mode/validation.js';
26
+ export { addGraphqlTools, resetGraphqlSchemaCache, GRAPHQL_TOOL_NAMES, SEARCH_GRAPHQL_TOOL_NAME, INSPECT_GRAPHQL_TOOL_NAME, EXECUTE_GRAPHQL_TOOL_NAME, WIX_GRAPHQL_ENDPOINT, type GraphqlToolsOptions, type GraphqlToolName } from './graphql/index.js';
26
27
  export { defaultConfig, defaultToolDescriptions, defaultReadmeDocs, widgetSiteCreationConfig, SITE_CREATION_COPY, toWidget, executeCodeModeToolDescriptions, SYSTEM_REMINDER_GUIDELINES, SYSTEM_REMINDER, mergeConfig, type ModeDefaultConfig } from './config/default-config.js';
27
28
  export * from './site-widget-tools/constants.js';
28
29
  //# sourceMappingURL=index.d.ts.map
@@ -6,7 +6,7 @@ export { handleWixAPIResponse } from './tool-utils.js';
6
6
  export { addDocsResources } from './resources/docs.js';
7
7
  export { addSupportTool } from './support/index.js';
8
8
  export { addGetProfileTool } from './account-tools/get-profile-tool.js';
9
- export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool } from './site-widget-tools/index.js';
9
+ export { addCreateSiteWidgetTool, addEditSiteWidgetTool, addPullSiteJobTool, addClaimSiteTool, addSuggestedDomainTool, addSiteCreationWidgetTools, addWixSiteCreationTool, addSearchSiteTemplatesTool, addCreateSiteFromTemplateTool } from './site-widget-tools/index.js';
10
10
  export * from './wixel/index.js';
11
11
  export { addImportClaudeDesignTool } from './import-claude-design-tool/index.js';
12
12
  export { IMPORT_CLAUDE_DESIGN_TOOL_NAME } from './import-claude-design-tool/constants.js';
@@ -27,6 +27,8 @@ export { generateDocsNavigationTree, fetchMenuContent, MENU_CONTENT_THRESHOLD, n
27
27
  // Code mode (search + execute via sandboxed JS)
28
28
  export { addCodeModeTools, addExecuteCodeModeTool, addSearchCodeModeTool } from './code-mode/index.js';
29
29
  export { validateExecuteCode, EXECUTE_FUNCTION_EXPRESSION_HINT } from './code-mode/validation.js';
30
+ // GraphQL tools (search operations + inspect schema + execute)
31
+ export { addGraphqlTools, resetGraphqlSchemaCache, GRAPHQL_TOOL_NAMES, SEARCH_GRAPHQL_TOOL_NAME, INSPECT_GRAPHQL_TOOL_NAME, EXECUTE_GRAPHQL_TOOL_NAME, WIX_GRAPHQL_ENDPOINT } from './graphql/index.js';
30
32
  // Config exports
31
33
  export { defaultConfig, defaultToolDescriptions, defaultReadmeDocs, widgetSiteCreationConfig, SITE_CREATION_COPY, toWidget, executeCodeModeToolDescriptions, SYSTEM_REMINDER_GUIDELINES, SYSTEM_REMINDER, mergeConfig } from './config/default-config.js';
32
34
  export * from './site-widget-tools/constants.js';
@@ -6,8 +6,10 @@ const WIX_APIS_BASE_URL = 'https://www.wixapis.com';
6
6
  // Sites keep this placeholder until the owner renames them, and domain search
7
7
  // rejects it as a blacklisted term.
8
8
  const DEFAULT_SITE_NAME = /^my site(\s+\d+)?$/i;
9
- function isDefaultSiteName(siteName) {
10
- return DEFAULT_SITE_NAME.test(siteName.trim());
9
+ // Domains are Latin, so the caller's query beats a site name in another script.
10
+ const NON_LATIN_LETTER = /(?![\p{Script=Latin}])\p{L}/u;
11
+ function isSearchableSiteName(siteName) {
12
+ return (!DEFAULT_SITE_NAME.test(siteName.trim()) && !NON_LATIN_LETTER.test(siteName));
11
13
  }
12
14
  async function fetchSiteDisplayName(siteAuthorization, siteId) {
13
15
  const response = await fetch(`${WIX_APIS_BASE_URL}/site-properties/v4/properties`, {
@@ -106,7 +108,7 @@ export function addSuggestedDomainTool(server, authStrategy) {
106
108
  structuredContent: { domains: [] }
107
109
  };
108
110
  }
109
- if (!isDefaultSiteName(siteName) || !resolvedQuery) {
111
+ if (isSearchableSiteName(siteName) || !resolvedQuery) {
110
112
  resolvedQuery = siteName;
111
113
  }
112
114
  }
@@ -314,6 +314,17 @@ export interface CodeValidationResult {
314
314
  error?: string;
315
315
  }
316
316
  export declare function validateExecuteCode(code: string): CodeValidationResult;
317
+ /** Wix "Auto GraphQL": one schema generated over all Wix public APIs. */
318
+ export declare const WIX_GRAPHQL_ENDPOINT = "https://www.wixapis.com/graphql/alpha";
319
+ export declare const SEARCH_GRAPHQL_TOOL_NAME = "SearchWixGraphQLOperations";
320
+ export declare const INSPECT_GRAPHQL_TOOL_NAME = "InspectWixGraphQLSchema";
321
+ export declare const EXECUTE_GRAPHQL_TOOL_NAME = "ExecuteWixGraphQL";
322
+ export declare const GRAPHQL_TOOL_NAMES: readonly [
323
+ "SearchWixGraphQLOperations",
324
+ "InspectWixGraphQLSchema",
325
+ "ExecuteWixGraphQL"
326
+ ];
327
+ export type GraphqlToolName = (typeof GRAPHQL_TOOL_NAMES)[number];
317
328
  /** URL for the management recipes index article */
318
329
  export declare const RECIPES_INDEX_URL = "https://dev.wix.com/docs/api-reference/articles/ai-tools/wix-mcp/skills/wix-management-recipes-index";
319
330
  /**
@@ -379,6 +390,7 @@ export interface ModeDefaultConfig {
379
390
  partnerContextEnabled?: boolean;
380
391
  usePlatformizedSearch?: boolean;
381
392
  skillsPrOverrideEnabled?: boolean;
393
+ graphqlEnabled?: boolean;
382
394
  }
383
395
  export declare const SYSTEM_REMINDER_GUIDELINES: string;
384
396
  export declare const SYSTEM_REMINDER: string;