@wix/mcp 1.0.0 → 1.0.1

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 @@
1
+ export const LONG_KB_CONTENT = "# Method name:\n\ncrm.contacts.queryContacts(options: QueryContactsOptions)\n\n# Method Link:\n\nhttps://dev.wix.com/docs/sdk/backend-modules/crm/contacts/query-contacts\n\n# Method Description:\n\nCreates a query to retrieve a list of contacts.\n\nThe `queryContacts()` function builds a query to retrieve a list of contacts and returns a `ContactsQueryBuilder` object.\n\nThe returned object contains the query definition, which is typically used to run the query using the `find()` function.\n\nYou can refine the query by chaining `ContactsQueryBuilder` functions onto the query. `ContactsQueryBuilder` functions enable you to sort, filter, and control the results `queryContacts()` returns.\n\n`queryContacts()` runs with these `ContactsQueryBuilder` defaults, which you can override:\n\n- `skip(0)`\n- `limit(50)`\n- `descending(\"_createdDate\")`\n\nThe functions that are chained to `queryContacts()` are applied in the order they are called. For example, if you apply `ascending('info.company')` and then `descending('info.name.last')`, the results are sorted first by the company name, and then, if there are multiple results with the same company, the items are sorted by last name.\n\n\n|PROPERTY\t|SUPPORTED FILTERS & SORTING\t\n|:---:|:---:|\n|`_id`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists)|\n|`_createdDate`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`gt()`](/contacts-query-builder/gt),[`lt()`](/contacts-query-builder/lt),[`ge()`](/contacts-query-builder/ge),[`le()`](/contacts-query-builder/le),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`_updatedDate`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`gt()`](/contacts-query-builder/gt),[`lt()`](/contacts-query-builder/lt),[`ge()`](/contacts-query-builder/ge),[`le()`](/contacts-query-builder/le)|\n|`lastActivity.activityDate`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`gt()`](/contacts-query-builder/gt),[`lt()`](/contacts-query-builder/lt),[`ge()`](/contacts-query-builder/ge),[`le()`](/contacts-query-builder/le),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`primaryInfo.email`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`primaryInfo.phone`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with)|\n|`info.name.first`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`info.name.last`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`info.company`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`info.jobTitle`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists),[`startsWith()`](/contacts-query-builder/starts-with),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`info.birthdate`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`ascending()`](/contacts-query-builder/ascending),[`descending()`](/contacts-query-builder/descending)|\n|`info.locale`|[`eq()`](/contacts-query-builder/eq),[`ne()`](/contacts-query-builder/ne),[`in()`](/contacts-query-builder/in),[`exists()`](/contacts-query-builder/exists)|\n\n# Method Permissions:\n\n- Manage Events\n- Read Contacts\n- Read Members and Contacts - all read permissions\n- Manage Restaurants - all permissions\n- Manage Contacts\n- Set Up Automations\n- Manage Members and Contacts - all permissions\n\n# Method Permissions Scopes IDs:\n\n- SCOPE.EVENTS.MANAGE-EVENTS\n- SCOPE.DC-CONTACTS.READ-CONTACTS\n- SCOPE.DC-CONTACTS-MEGA.READ-MEMBERS-CONTACTS\n- SCOPE.RESTAURANTS.MEGA-SCOPES\n- SCOPE.DC-CONTACTS.MANAGE-CONTACTS\n- SCOPE.CRM.SETUP-AUTOMATIONS\n- SCOPE.DC-CONTACTS-MEGA.MANAGE-MEMBERS-CONTACTS\n\n# Method Schema Summary:\n\n# API Method Summary: `queryContacts`\n\n## Method Signature\n```javascript\ncrm.contacts.queryContacts(options)\n```\n\n## Description\nThe `queryContacts()` method creates a query to retrieve a list of contacts and returns a `ContactsQueryBuilder` object. This object allows for further refinement of the query through chaining methods to sort, filter, and control the results. The default settings for the query are:\n- `skip(0)`\n- `limit(50)`\n- `descending(\"_createdDate\")`\n\n## Parameters\n- **options**: `QueryContactsOptions`\n - Description: Options for querying contacts.\n\n## Return Type\n- **REFERENCE<ContactsQueryBuilder>**: The method returns a `ContactsQueryBuilder` object that can be used to refine the query further.\n\n## Supported Filters & Sorting\nThe following properties can be used for filtering and sorting, along with their supported functions:\n\n| Property | Supported Functions |\n|------------------------------|---------------------------------------------------------------------------------------------------------|\n| `_id` | `eq()`, `ne()`, `in()`, `exists()` |\n| `_createdDate` | `eq()`, `ne()`, `gt()`, `lt()`, `ge()`, `le()`, `ascending()`, `descending()` |\n| `_updatedDate` | `eq()`, `ne()`, `gt()`, `lt()`, `ge()`, `le()` |\n| `lastActivity.activityDate` | `eq()`, `ne()`, `gt()`, `lt()`, `ge()`, `le()`, `ascending()`, `descending()` |\n| `primaryInfo.email` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()`, `ascending()`, `descending()` |\n| `primaryInfo.phone` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()` |\n| `info.name.first` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()`, `ascending()`, `descending()` |\n| `info.name.last` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()`, `ascending()`, `descending()` |\n| `info.company` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()`, `ascending()`, `descending()` |\n| `info.jobTitle` | `eq()`, `ne()`, `in()`, `exists()`, `startsWith()`, `ascending()`, `descending()` |\n| `info.birthdate` | `eq()`, `ne()`, `ascending()`, `descending()` |\n| `info.locale` | `eq()`, `ne()`, `in()`, `exists()` |\n\n## Enum Properties\nThe following properties have limited allowed values (enums):\n\n- **lastActivity.activityType**: \n - Valid values: `ARENA_NEW_LEAD`, `BOOKINGS_APPOINTMENT`, `CASHIER_BUTTON_PURCHASE`, `CONTACT_CREATED`, `CONTACT_MERGED`, `ECOM_CART_ABANDON`, `ECOM_CHECKOUT_BUYER`, `ECOM_PURCHASE`, `EVENTS_RSVP`, `FORM_SUBMITTED`, `GENERAL`, `HOTELS_CANCEL`, `HOTELS_CONFIRMATION`, `HOTELS_PURCHASE`, `HOTELS_RESERVATION`, `INBOX_FORM_SUBMITTED`, `INBOX_MESSAGE_FROM_CUSTOMER`, `INBOX_MESSAGE_TO_CUSTOMER`, `INBOX_PAYMENT_REQUEST_PAID`, `INVOICE_OVERDUE`, `INVOICE_PAY`, `MEMBER_LOGIN`, `MEMBER_REGISTER`, `MEMBER_STATUS_CHANGED`, `NEWSLETTER_SUBSCRIPTION_FORM_SUBMITTED`, `NEWSLETTER_SUBSCRIPTION_NOT_SET`, `NEWSLETTER_SUBSCRIPTION_PENDING`, `NEWSLETTER_SUBSCRIPTION_SUBSCRIBE`, `NEWSLETTER_SUBSCRIPTION_UNSUBSCRIBE`, `PHONE_SUBSCRIPTION_NOT_SET`, `PHONE_SUBSCRIPTION_PENDING`, `PHONE_SUBSCRIPTION_SUBSCRIBE`, `PHONE_SUBSCRIPTION_UNSUBSCRIBE`, `PRICE_QUOTE_ACCEPT`, `PRICE_QUOTE_EXPIRE`, `RESTAURANTS_ORDER`, `RESTAURANTS_RESERVATION`, `SHOUTOUT_CLICK`, `SHOUTOUT_OPEN`, `VIDEO_PURCHASE`, `VIDEO_RENT`\n\n- **primaryEmail.deliverabilityStatus**: \n - Valid values: `BOUNCED`, `INACTIVE`, `NOT_SET`, `SPAM_COMPLAINT`, `VALID`\n\n- **primaryEmail.subscriptionStatus**: \n - Valid values: `NOT_SET`, `NO_SUBSCRIPTION_STATUS`, `PENDING`, `SUBSCRIBED`, `UNSUBSCRIBED`\n\n- **primaryPhone.subscriptionStatus**: \n - Valid values: `NOT_SET`, `NO_SUBSCRIPTION_STATUS`\n\n- **info.emails.items[].tag**: \n - Valid values: `HOME`, `MAIN`, `UNTAGGED`, `WORK`\n\n- **info.phones.items[].tag**: \n - Valid values: `FAX`, `HOME`, `MAIN`, `MOBILE`, `UNTAGGED`, `WORK`\n\n- **items[].info.addresses.items[].tag**: \n - Valid values: `BILLING`, `HOME`, `SHIPPING`, `UNTAGGED`, `WORK`\n\n**Note**: Ensure to use the correct enum values when filtering or sorting based on these properties. \n\n## Possible Errors\nThe following errors may occur when using this method:\n- **400 INVALID_ARGUMENT**: Various application codes indicating unsupported filter value types, unsupported filter values, unsupported filter operators, unsupported filter fields, malformed filters, illegal limits, or unsupported sort fields.\n\n# Method Code Examples:\n\n## queryContacts\n\n```javascript\nimport { contacts } from '@wix/crm';\n\nasync function queryContacts() {\n const { items } = await contacts.queryContacts().find();\n};\n```\n\n\n\n## default\n\n```javascript\ntry {\n const { items } = await contacts.queryContacts().find();\n return items;\n } catch (error) {\n console.error(error);\n throw error;\n }\n````";
@@ -0,0 +1,10 @@
1
+ import type { DocsTool } from './docs.js';
2
+ export declare const MAX_CONTENT_LENGTH = 10000;
3
+ export declare const DEFAULT_MAX_RESULTS = 10;
4
+ export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxResults, rerank, linesInEachResult }: {
5
+ toolName: DocsTool;
6
+ toolParams: Record<string, string>;
7
+ maxResults?: number;
8
+ rerank?: boolean;
9
+ linesInEachResult?: number | undefined;
10
+ }) => Promise<string>;
@@ -1,5 +1,6 @@
1
1
  import { logger } from '../logger.js';
2
- const MAX_CONTENT_LENGTH = 10000;
2
+ export const MAX_CONTENT_LENGTH = 10000;
3
+ export const DEFAULT_MAX_RESULTS = 10;
3
4
  const runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank = false) => {
4
5
  const startTime = new Date().getTime();
5
6
  const url = new URL(`https://www.wixapis.com/mcp-docs-search/v1/search?maxResults=${maxResults}&rerank=${rerank}`);
@@ -7,9 +8,12 @@ const runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank =
7
8
  url.searchParams.append('kbName', 'SDK_SCHEMAS_KB_ID');
8
9
  url.searchParams.append('kbName', 'SDK_DOCS_KB_ID');
9
10
  }
10
- else if (toolName === 'REST') {
11
- url.searchParams.append('kbName', 'REST_METHODS_KB_ID');
11
+ else if (toolName === 'REST' || toolName === 'BUSINESS_SOLUTIONS') {
12
+ if (toolName === 'REST') {
13
+ url.searchParams.append('kbName', 'REST_METHODS_KB_ID');
14
+ }
12
15
  url.searchParams.append('kbName', 'REST_DOCS_KB_ID');
16
+ url.searchParams.append('kbName', 'MCP_REST_RECIPES_KB_ID');
13
17
  }
14
18
  else if (toolName === 'WDS') {
15
19
  url.searchParams.append('kbName', 'WDS_DOCS_KB_ID');
@@ -28,8 +32,7 @@ const runSemanticSearch = async (toolName, toolParams, maxResults = 20, rerank =
28
32
  headers: {
29
33
  'Content-Type': 'application/json',
30
34
  'x-time-budget': '180000',
31
- 'x-wix-time-budget': '180000',
32
- Cookie: process.env.WIX_COOKIE || ''
35
+ 'x-wix-time-budget': '180000'
33
36
  }
34
37
  });
35
38
  if (!response.ok) {
@@ -52,7 +55,7 @@ const getContentFromChunkedContent = (chunkedContent) => {
52
55
  }
53
56
  return content;
54
57
  };
55
- const formatResultsContent = (resultsFromKbs, maxResults) => {
58
+ const formatResultsContent = (resultsFromKbs, maxResults, linesInEachResult = undefined) => {
56
59
  // First, take the results from all KBs and sort them by overallMatchScore
57
60
  const allResults = Object.entries(resultsFromKbs)
58
61
  .map(([, { results }]) => results)
@@ -65,11 +68,29 @@ const formatResultsContent = (resultsFromKbs, maxResults) => {
65
68
  const topResults = sortedResults.slice(0, maxResults);
66
69
  // Concatenate the content of the top results
67
70
  const concatenatedContent = topResults
71
+ .map((result) => {
72
+ let newContent = result.entry.content;
73
+ if (linesInEachResult) {
74
+ const totalLines = newContent.split('\n').length;
75
+ const linesToTake = Math.min(linesInEachResult, totalLines);
76
+ newContent = newContent.split('\n').slice(0, linesToTake).join('\n');
77
+ const moreLines = totalLines - linesToTake;
78
+ const moreLinesText = moreLines > 0 ? `\n\n... ${moreLines} more lines ...` : '';
79
+ newContent = `${newContent}${moreLinesText}`;
80
+ }
81
+ return {
82
+ ...result,
83
+ entry: {
84
+ ...result.entry,
85
+ content: newContent
86
+ }
87
+ };
88
+ })
68
89
  .map((result) => {
69
90
  const content = result.entry.content;
70
91
  const chunkedContent = result.entry.chunkedContent;
71
92
  if (content.length > MAX_CONTENT_LENGTH) {
72
- logger.log(`Content is too long, using first chunk`, {
93
+ logger.log(`Content is too long, using chunked content`, {
73
94
  contentLength: content.length,
74
95
  chunkedContentLength: chunkedContent.length
75
96
  });
@@ -80,7 +101,8 @@ const formatResultsContent = (resultsFromKbs, maxResults) => {
80
101
  .join('\n\n---\n\n');
81
102
  return concatenatedContent;
82
103
  };
83
- export const runSemanticSearchAndFormat = async (toolName, toolParams, maxResults = 20, rerank = false) => {
104
+ export const runSemanticSearchAndFormat = async ({ toolName, toolParams, maxResults = DEFAULT_MAX_RESULTS, rerank, linesInEachResult }) => {
84
105
  const results = await runSemanticSearch(toolName, toolParams, maxResults, rerank);
85
- return formatResultsContent(results, Math.min(maxResults, 10));
106
+ const maxResultsToUse = Math.min(maxResults, DEFAULT_MAX_RESULTS);
107
+ return formatResultsContent(results, maxResultsToUse, linesInEachResult);
86
108
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,13 @@
1
1
  import { describe, test, expect, beforeEach, vi, afterAll } from 'vitest';
2
- import { runSemanticSearchAndFormat } from './semanticSearch.js';
2
+ import { DEFAULT_MAX_RESULTS, runSemanticSearchAndFormat } from './semanticSearch.js';
3
+ import { LONG_KB_CONTENT } from './long-content.js';
3
4
  const sampleSearchResponse = {
4
5
  results: {
5
6
  SDK_DOCS_KB_ID: {
6
7
  results: [
7
8
  {
8
9
  entry: {
9
- content: 'SDK Documentation content 1'
10
+ content: 'SDK Documentation content 1\nline 2\nline 3'
10
11
  },
11
12
  scoresInfo: {
12
13
  overallMatchScore: 0.95
@@ -19,6 +20,15 @@ const sampleSearchResponse = {
19
20
  scoresInfo: {
20
21
  overallMatchScore: 0.85
21
22
  }
23
+ },
24
+ {
25
+ entry: {
26
+ content: LONG_KB_CONTENT,
27
+ chunkedContent: ['some-chunked-content']
28
+ },
29
+ scoresInfo: {
30
+ overallMatchScore: 0.83
31
+ }
22
32
  }
23
33
  ]
24
34
  },
@@ -45,6 +55,18 @@ const sampleSearchResponse = {
45
55
  }
46
56
  }
47
57
  ]
58
+ },
59
+ MCP_REST_RECIPES_KB_ID: {
60
+ results: [
61
+ {
62
+ entry: {
63
+ content: 'Build Apps content 1'
64
+ },
65
+ scoresInfo: {
66
+ overallMatchScore: 0.92
67
+ }
68
+ }
69
+ ]
48
70
  }
49
71
  }
50
72
  };
@@ -109,13 +131,16 @@ describe('Semantic Search', () => {
109
131
  status: 200,
110
132
  json: async () => sdkResponse
111
133
  });
112
- const result = await runSemanticSearchAndFormat('SDK', {
113
- searchTerm: 'wix-data query'
134
+ const result = await runSemanticSearchAndFormat({
135
+ toolName: 'SDK',
136
+ toolParams: {
137
+ searchTerm: 'wix-data query'
138
+ }
114
139
  });
115
140
  expect(fetchMock).toHaveBeenCalledTimes(1);
116
141
  const [url] = fetchMock.mock.calls[0];
117
142
  expect(url).toContain('mcp-docs-search/v1/search');
118
- expect(url).toContain('maxResults=20');
143
+ expect(url).toContain(`maxResults=${DEFAULT_MAX_RESULTS}`);
119
144
  expect(url).toContain('rerank=false');
120
145
  expect(url).toContain('kbName=SDK_SCHEMAS_KB_ID');
121
146
  expect(url).toContain('kbName=SDK_DOCS_KB_ID');
@@ -157,15 +182,54 @@ describe('Semantic Search', () => {
157
182
  status: 200,
158
183
  json: async () => restResponse
159
184
  });
160
- const result = await runSemanticSearchAndFormat('REST', {
161
- searchTerm: 'create collection'
185
+ const result = await runSemanticSearchAndFormat({
186
+ toolName: 'REST',
187
+ toolParams: {
188
+ searchTerm: 'create collection'
189
+ }
162
190
  });
163
191
  expect(fetchMock).toHaveBeenCalledTimes(1);
164
192
  const [url] = fetchMock.mock.calls[0];
165
193
  expect(url).toContain('kbName=REST_METHODS_KB_ID');
166
194
  expect(url).toContain('kbName=REST_DOCS_KB_ID');
195
+ expect(url).toContain('kbName=MCP_REST_RECIPES_KB_ID');
167
196
  expect(result).toBe('REST Methods content\n\n---\n\nREST Documentation content');
168
197
  });
198
+ test('performs BUSINESS_SOLUTIONS semantic search successfully', async () => {
199
+ const restResponse = {
200
+ results: {
201
+ REST_DOCS_KB_ID: {
202
+ results: [
203
+ {
204
+ entry: {
205
+ content: 'REST Documentation content'
206
+ },
207
+ scoresInfo: {
208
+ overallMatchScore: 0.95
209
+ }
210
+ }
211
+ ]
212
+ }
213
+ }
214
+ };
215
+ fetchMock.mockResolvedValueOnce({
216
+ ok: true,
217
+ status: 200,
218
+ json: async () => restResponse
219
+ });
220
+ const result = await runSemanticSearchAndFormat({
221
+ toolName: 'BUSINESS_SOLUTIONS',
222
+ toolParams: {
223
+ searchTerm: 'create collection'
224
+ }
225
+ });
226
+ expect(fetchMock).toHaveBeenCalledTimes(1);
227
+ const [url] = fetchMock.mock.calls[0];
228
+ expect(url).not.toContain('kbName=REST_METHODS_KB_ID');
229
+ expect(url).toContain('kbName=REST_DOCS_KB_ID');
230
+ expect(url).toContain('kbName=MCP_REST_RECIPES_KB_ID');
231
+ expect(result).toBe('REST Documentation content');
232
+ });
169
233
  test('performs WDS semantic search successfully', async () => {
170
234
  const wdsResponse = {
171
235
  results: {
@@ -188,7 +252,12 @@ describe('Semantic Search', () => {
188
252
  status: 200,
189
253
  json: async () => wdsResponse
190
254
  });
191
- const result = await runSemanticSearchAndFormat('WDS', { searchTerm: 'Button component' }, 1);
255
+ const result = await runSemanticSearchAndFormat({
256
+ toolName: 'WDS',
257
+ toolParams: {
258
+ searchTerm: 'Button component'
259
+ }
260
+ });
192
261
  expect(fetchMock).toHaveBeenCalledTimes(1);
193
262
  const [url] = fetchMock.mock.calls[0];
194
263
  expect(url).toContain('kbName=WDS_DOCS_KB_ID');
@@ -216,7 +285,12 @@ describe('Semantic Search', () => {
216
285
  status: 200,
217
286
  json: async () => buildAppsResponse
218
287
  });
219
- const result = await runSemanticSearchAndFormat('BUILD_APPS', { searchTerm: 'deploy app' }, 5);
288
+ const result = await runSemanticSearchAndFormat({
289
+ toolName: 'BUILD_APPS',
290
+ toolParams: {
291
+ searchTerm: 'deploy app'
292
+ }
293
+ });
220
294
  expect(fetchMock).toHaveBeenCalledTimes(1);
221
295
  const [url] = fetchMock.mock.calls[0];
222
296
  expect(url).toContain('kbName=BUILD_APPS_KB_ID');
@@ -244,8 +318,11 @@ describe('Semantic Search', () => {
244
318
  status: 200,
245
319
  json: async () => wixHeadlessResponse
246
320
  });
247
- const result = await runSemanticSearchAndFormat('WIX_HEADLESS', {
248
- searchTerm: 'query'
321
+ const result = await runSemanticSearchAndFormat({
322
+ toolName: 'WIX_HEADLESS',
323
+ toolParams: {
324
+ searchTerm: 'query'
325
+ }
249
326
  });
250
327
  expect(fetchMock).toHaveBeenCalledTimes(1);
251
328
  const [url] = fetchMock.mock.calls[0];
@@ -258,7 +335,12 @@ describe('Semantic Search', () => {
258
335
  status: 500,
259
336
  statusText: 'Internal Server Error'
260
337
  });
261
- await expect(runSemanticSearchAndFormat('SDK', { searchTerm: 'query' })).rejects.toThrow('Failed to run rag search: Internal Server Error');
338
+ await expect(runSemanticSearchAndFormat({
339
+ toolName: 'SDK',
340
+ toolParams: {
341
+ searchTerm: 'query'
342
+ }
343
+ })).rejects.toThrow('Failed to run rag search: Internal Server Error');
262
344
  });
263
345
  test('handles empty results', async () => {
264
346
  fetchMock.mockResolvedValueOnce({
@@ -266,24 +348,112 @@ describe('Semantic Search', () => {
266
348
  status: 200,
267
349
  json: async () => ({ results: {} })
268
350
  });
269
- await expect(runSemanticSearchAndFormat('SDK', { searchTerm: 'query' })).rejects.toThrow('No result from search');
351
+ await expect(runSemanticSearchAndFormat({
352
+ toolName: 'SDK',
353
+ toolParams: {
354
+ searchTerm: 'query'
355
+ }
356
+ })).rejects.toThrow('No result from search');
270
357
  });
271
358
  test('respects maxResults parameter', async () => {
272
359
  const maxResults = 5;
273
- await runSemanticSearchAndFormat('SDK', { searchTerm: 'query' }, maxResults);
360
+ await runSemanticSearchAndFormat({
361
+ toolName: 'SDK',
362
+ toolParams: {
363
+ searchTerm: 'query'
364
+ },
365
+ maxResults
366
+ });
274
367
  const [url] = fetchMock.mock.calls[0];
275
368
  expect(url).toContain('maxResults=5');
276
369
  });
277
370
  test('respects rerank parameter', async () => {
278
- await runSemanticSearchAndFormat('SDK', { searchTerm: 'query' }, 5, true);
371
+ await runSemanticSearchAndFormat({
372
+ toolName: 'SDK',
373
+ toolParams: {
374
+ searchTerm: 'query'
375
+ },
376
+ maxResults: 5,
377
+ rerank: true
378
+ });
279
379
  const [url] = fetchMock.mock.calls[0];
280
380
  expect(url).toContain('rerank=true');
281
381
  });
282
- test('uses WIX_COOKIE when available', async () => {
283
- process.env.WIX_COOKIE = 'test-cookie';
284
- await runSemanticSearchAndFormat('SDK', { searchTerm: 'query' });
285
- expect(fetchMock).toHaveBeenCalledTimes(1);
286
- const [, options] = fetchMock.mock.calls[0];
287
- expect(options.headers.Cookie).toBe('test-cookie');
382
+ describe('light mode and long content', () => {
383
+ test('respects no linesInEachResult parameter - default behavior', async () => {
384
+ const result = await runSemanticSearchAndFormat({
385
+ toolName: 'SDK',
386
+ toolParams: {
387
+ searchTerm: 'query'
388
+ },
389
+ maxResults: 5,
390
+ rerank: false
391
+ });
392
+ expect(result).toContain('line 3');
393
+ });
394
+ test('respects linesInEachResult parameter', async () => {
395
+ const result = await runSemanticSearchAndFormat({
396
+ toolName: 'SDK',
397
+ toolParams: {
398
+ searchTerm: 'query'
399
+ },
400
+ maxResults: 5,
401
+ rerank: false,
402
+ linesInEachResult: 2
403
+ });
404
+ expect(result).toContain('line 2');
405
+ });
406
+ test('respects linesInEachResult parameter', async () => {
407
+ const result = await runSemanticSearchAndFormat({
408
+ toolName: 'SDK',
409
+ toolParams: {
410
+ searchTerm: 'query'
411
+ },
412
+ maxResults: 5,
413
+ rerank: false,
414
+ linesInEachResult: 1
415
+ });
416
+ expect(result).not.toContain('line 2');
417
+ });
418
+ test('respects linesInEachResult even if content is too long', async () => {
419
+ const longContentLines = LONG_KB_CONTENT.split('\n');
420
+ const longContentLine1 = longContentLines[0];
421
+ const longContentLine16 = longContentLines[16];
422
+ const result = await runSemanticSearchAndFormat({
423
+ toolName: 'SDK',
424
+ toolParams: {
425
+ searchTerm: 'query'
426
+ },
427
+ maxResults: 10,
428
+ rerank: false,
429
+ linesInEachResult: 15
430
+ });
431
+ expect(result).toContain(longContentLine1);
432
+ expect(result).not.toContain('some-chunked-content');
433
+ expect(result).not.toContain(longContentLine16);
434
+ });
435
+ test('uses chunked content if content is too long and linesInEachResult is not set', async () => {
436
+ const result = await runSemanticSearchAndFormat({
437
+ toolName: 'SDK',
438
+ toolParams: {
439
+ searchTerm: 'query'
440
+ },
441
+ maxResults: 10,
442
+ rerank: false
443
+ });
444
+ expect(result).toContain('some-chunked-content');
445
+ });
446
+ test('uses chunked content if content is too long and linesInEachResult is set', async () => {
447
+ const result = await runSemanticSearchAndFormat({
448
+ toolName: 'SDK',
449
+ toolParams: {
450
+ searchTerm: 'query'
451
+ },
452
+ maxResults: 10,
453
+ rerank: false,
454
+ linesInEachResult: 1500
455
+ });
456
+ expect(result).toContain('some-chunked-content');
457
+ });
288
458
  });
289
459
  });
@@ -0,0 +1,5 @@
1
+ export { WixMcpServer } from './wix-mcp-server.js';
2
+ export { addDocsTools, DocsTool, VALID_DOCS_TOOLS } from './docs/docs.js';
3
+ export { addApiCallTool } from './api-call/index.js';
4
+ export { handleWixAPIResponse } from './tool-utils.js';
5
+ export { addDocsResources } from './resources/docs.js';
package/build/index.js CHANGED
@@ -1,109 +1,5 @@
1
- import './sentry.js';
2
- import minimist from 'minimist';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { addDocsTools, VALID_DOCS_TOOLS } from './docs/docs.js';
5
- import { addCliTools, VALID_CLI_TOOLS } from './cli-tools/cli.js';
6
- import { logger, attachStdErrLogger, attachMcpServerLogger, attachFileLogger } from './logger.js';
7
- import { WixMcpServer } from './wix-mcp-server.js';
8
- import { addDocsResources } from './resources/docs.js';
9
- const VALID_TOOLS = [
10
- 'WDS',
11
- 'REST',
12
- 'SDK',
13
- 'BUILD_APPS',
14
- 'WIX_HEADLESS'
15
- ];
16
- const EXPERIMENTAL_TOOLS = ['WIX_API', 'CLI_COMMAND'];
17
- export const DEFAULT_TOOLS = [
18
- 'WDS',
19
- 'REST',
20
- 'SDK',
21
- 'BUILD_APPS',
22
- 'WIX_HEADLESS'
23
- ];
24
- const parsedArgs = minimist(process.argv.slice(2));
25
- function parseExperimentalArg() {
26
- const experimentalArg = parsedArgs['experimental'];
27
- if (!experimentalArg)
28
- return [];
29
- return experimentalArg
30
- .split(',')
31
- .map((t) => t.trim())
32
- .filter((tool) => EXPERIMENTAL_TOOLS.includes(tool));
33
- }
34
- function parseToolsArg() {
35
- const toolsArg = parsedArgs['tools'];
36
- const experimentalTools = parseExperimentalArg();
37
- if (!toolsArg) {
38
- // When no tools specified, return both default and experimental tools
39
- return [...DEFAULT_TOOLS, ...experimentalTools];
40
- }
41
- const requestedTools = toolsArg.split(',').map((t) => t.trim());
42
- const tools = [
43
- // Include valid non-experimental tools
44
- ...requestedTools.filter((tool) => VALID_TOOLS.includes(tool)),
45
- // Include enabled experimental tools
46
- ...experimentalTools
47
- ];
48
- // Warn about enabled experimental tools
49
- tools.forEach((tool) => {
50
- if (EXPERIMENTAL_TOOLS.includes(tool)) {
51
- logger.log(`Warning: ${tool} is an experimental tool and may have limited functionality or breaking changes`);
52
- }
53
- });
54
- return tools;
55
- }
56
- const loggerType = parsedArgs['logger'] || 'mcp';
57
- if (loggerType === 'file') {
58
- attachFileLogger();
59
- }
60
- else {
61
- // Initially we log to stderr, because MCP server is not connected yet
62
- // When the server is connected, we attach the MCP server logger
63
- attachStdErrLogger();
64
- }
65
- logger.log('--------------------------------');
66
- logger.log('starting WIX MCP server');
67
- logger.log('--------------------------------');
68
- const server = new WixMcpServer({
69
- name: 'wix-mcp-server',
70
- version: '1.0.0'
71
- }, {
72
- capabilities: {
73
- tools: {},
74
- prompts: {},
75
- logging: {},
76
- resources: {}
77
- }
78
- });
79
- const activeTools = parseToolsArg();
80
- logger.log('Active tools:', activeTools);
81
- const docsTools = activeTools.filter((tool) => VALID_DOCS_TOOLS.includes(tool));
82
- if (docsTools.length > 0) {
83
- logger.log('Adding docs tools:', docsTools);
84
- addDocsTools(server, docsTools);
85
- }
86
- const cliTools = activeTools.filter((tool) => VALID_CLI_TOOLS.includes(tool));
87
- if (cliTools.length > 0) {
88
- logger.log('Adding cli tools:', cliTools);
89
- addCliTools(server, cliTools);
90
- }
91
- try {
92
- const portals = parsedArgs['portals']?.split(',') || [];
93
- if (portals.length > 0) {
94
- logger.log('Adding docs resources for portals:', portals);
95
- await addDocsResources(server, portals);
96
- }
97
- }
98
- catch (error) {
99
- logger.error('Error adding docs resources:', error);
100
- }
101
- logger.log('Starting server');
102
- const transport = new StdioServerTransport();
103
- logger.log('Connecting to transport');
104
- await server.connect(transport);
105
- logger.log('Transport connected');
106
- if (loggerType === 'mcp') {
107
- // From now on, we log to the MCP server
108
- attachMcpServerLogger(server);
109
- }
1
+ export { WixMcpServer } from './wix-mcp-server.js';
2
+ export { addDocsTools, VALID_DOCS_TOOLS } from './docs/docs.js';
3
+ export { addApiCallTool } from './api-call/index.js';
4
+ export { handleWixAPIResponse } from './tool-utils.js';
5
+ export { addDocsResources } from './resources/docs.js';
@@ -0,0 +1,6 @@
1
+ interface Options {
2
+ timeout?: number;
3
+ interval?: number;
4
+ }
5
+ export default function eventually<ReturnType = void>(expectation: () => ReturnType | Promise<ReturnType>, options?: Options): Promise<ReturnType>;
6
+ export {};
@@ -0,0 +1,13 @@
1
+ import Stream from 'node:stream';
2
+ export declare function handleStdout(stdout: Stream.Readable): {
3
+ waitForText(match: string | RegExp, { timeout }?: {
4
+ timeout?: number | undefined;
5
+ }): Promise<string | undefined>;
6
+ waitForAndCallback(matchesAndCallbacks: {
7
+ match: string;
8
+ callback: (stop: () => void) => void;
9
+ }[], { timeout }?: {
10
+ timeout?: number | undefined;
11
+ }): Promise<unknown>;
12
+ getOutput(): Promise<string>;
13
+ };
@@ -0,0 +1,7 @@
1
+ import Stream from 'node:stream';
2
+ export declare const ENTER = "\r";
3
+ export declare const mockInteractiveGenerateCommandTool: ({ extensionType, stdin, stdout }: {
4
+ extensionType: string;
5
+ stdin: Stream.Writable;
6
+ stdout: Stream.Readable;
7
+ }) => Promise<string>;
@@ -0,0 +1,10 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ interface Logger {
3
+ log(...data: unknown[]): void;
4
+ error(...data: unknown[]): void;
5
+ }
6
+ export declare const logger: Logger;
7
+ export declare const attachMcpServerLogger: (server: McpServer) => void;
8
+ export declare const attachStdErrLogger: () => void;
9
+ export declare const attachFileLogger: () => void;
10
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const globalConfig: import("@wix/panorama-client-node").GlobalConfig<import("@wix/panorama-common-shared/dist/types/types/data-types.js").EventPayload, {}>;
2
+ export declare const panoramaFactory: import("@wix/panorama-client").PanoramaClientFactory<import("@wix/panorama-common-shared/dist/types/types/data-types.js").EventPayload>;
package/build/panorama.js CHANGED
@@ -1,7 +1,12 @@
1
- import { createRequire } from 'module';
1
+ // import { createRequire } from 'module';
2
2
  import { createGlobalConfig, panoramaClientFactory, PanoramaPlatform } from '@wix/panorama-client-node';
3
- const require = createRequire(import.meta.url);
4
- const packageJson = require('../package.json');
3
+ // const require = createRequire(import.meta.url);
4
+ // const packageJson = require('../package.json');
5
+ // TODO: use real package.json
6
+ // I temporarily disabled this because it's not working in the remote server
7
+ const packageJson = {
8
+ version: '1.0.0'
9
+ };
5
10
  export const globalConfig = createGlobalConfig();
6
11
  export const panoramaFactory = panoramaClientFactory({
7
12
  baseParams: {
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const addDocsResources: (server: McpServer, portals: string[]) => Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const handleWixAPIResponse: <T = any>(response: Response) => Promise<T>;
@@ -0,0 +1,29 @@
1
+ const safeParseJSON = (text) => {
2
+ try {
3
+ return JSON.parse(text);
4
+ }
5
+ catch {
6
+ return text;
7
+ }
8
+ };
9
+ export const handleWixAPIResponse = async (response) => {
10
+ const responseText = await response.text();
11
+ const responseData = safeParseJSON(responseText);
12
+ if (!response.ok) {
13
+ const requestId = response.headers.get('x-wix-request-id');
14
+ const errorDetails = typeof responseData === 'object'
15
+ ? JSON.stringify(responseData)
16
+ : responseData;
17
+ throw new Error([
18
+ `Failed to call Wix API: ${response.status} ${response.statusText}.`,
19
+ requestId ? `request id: ${requestId}` : '',
20
+ // Wix 404 for API does not exist is huge (generic 404 page) and loaded to the context
21
+ response.status === 404 && errorDetails.includes('<html>')
22
+ ? 'Not found'
23
+ : errorDetails
24
+ ]
25
+ .filter((str) => !!str)
26
+ .join('\n'));
27
+ }
28
+ return responseData;
29
+ };
@@ -0,0 +1 @@
1
+ export {};