@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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ args: ["/Users/absolute/path/to/repo/dir/."]
18
18
 
19
19
  ```
20
20
  command: node
21
- args: ["/Users/absolute/path/to/build/index.js"]
21
+ args: ["/Users/absolute/path/to/build/bin.js"]
22
22
  ```
23
23
 
24
24
  # Optional config arguments:
@@ -63,6 +63,11 @@ It's exactly parallel to the real docs url, for example:
63
63
  Docs URL: https://dev.wix.com/docs/picasso/wix-ai-docs/best-practices/sdk-best-practices
64
64
  Resource URI: wix-docs://picasso/wix-ai-docs/best-practices/sdk-best-practices
65
65
 
66
+ pass:
67
+ `--portals=<docs-portal-name>`
68
+ to load all docs from the portal as resources
69
+
70
+
66
71
  # Cursor
67
72
 
68
73
  ## Cursor MCP docs:
@@ -78,9 +83,10 @@ https://docs.cursor.com/context/model-context-protocol
78
83
  ```
79
84
  {
80
85
  "mcpServers": {
81
- "IGOR_TOOLS": {
86
+ "wix-local-mcp": {
82
87
  "command": "npx",
83
88
  "args": [
89
+ "-y",
84
90
  "@wix/mcp"
85
91
  ]
86
92
  }
@@ -92,7 +98,7 @@ https://docs.cursor.com/context/model-context-protocol
92
98
  ```
93
99
  {
94
100
  "mcpServers": {
95
- "IGOR_TOOLS": {
101
+ "wix-local-mcp": {
96
102
  "command": "npx",
97
103
  "args": [
98
104
  "/Users/absolute/path/to/repo/dir/."
@@ -110,10 +116,10 @@ https://docs.cursor.com/context/model-context-protocol
110
116
  ```
111
117
  {
112
118
  "mcpServers": {
113
- "IGOR_TOOLS": {
119
+ "wix-local-mcp": {
114
120
  "command": "node",
115
121
  "args": [
116
- "/Users/absolute/path/to/build/index.js"
122
+ "/Users/absolute/path/to/build/bin.js"
117
123
  ]
118
124
  }
119
125
  }
@@ -123,7 +129,9 @@ https://docs.cursor.com/context/model-context-protocol
123
129
  ### You can try using bun + index.ts
124
130
 
125
131
  # Troubleshooting
126
-
132
+ - check the logs from claude desktop / cursor
133
+ - is the error related to node / fnm / nvm ? make sure you have the latest node version as the default on in the path
134
+ - is the error related to npx? make sure you use -y and the correct npm registry
127
135
  - try including full path to node
128
136
  - try using bun and index.ts directly
129
137
  - make sure build files have permissions for cursor to access
package/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import './build/index.js';
2
+ import './build/bin.js';
@@ -0,0 +1,2 @@
1
+ import { WixMcpServer } from '../wix-mcp-server.js';
2
+ export declare function addApiCallTool(server: WixMcpServer, getSiteAccessToken: (siteId: string) => Promise<string>, getAccountAccessToken: () => Promise<string>): void;
@@ -0,0 +1,117 @@
1
+ import { z } from 'zod';
2
+ import { logger } from '../logger.js';
3
+ import { handleWixAPIResponse } from '../tool-utils.js';
4
+ export function addApiCallTool(server, getSiteAccessToken, getAccountAccessToken) {
5
+ server.tool('CallWixSiteAPI', 'Call Wix apis on a business or site. Use this to create, read, update, and delete data and other Wix entities in your Wix site,' +
6
+ `You should ALWAYS check the rest docs - "SearchWixRESTDocumentation" for the specific API you want to call, don't just call it without knowing what it does, CHECK THE DOCS`, {
7
+ siteId: z
8
+ .string()
9
+ .describe('The id of the site selected using site selection tool'),
10
+ url: z
11
+ .string()
12
+ .describe('The url of the api to call - ALWAYS get the information from the Wix REST docs or from the conversation context, the URL MUST BE ABSOLUTE URL'),
13
+ method: z
14
+ .string()
15
+ .describe('The HTTP method to use for the API call (e.g. GET, POST, PUT, DELETE)'),
16
+ body: z
17
+ .string()
18
+ .optional()
19
+ .describe('A string representing of a valid JSON object to describe the body of the request')
20
+ }, async ({ url, body, method, siteId }) => {
21
+ logger.log(`Calling Wix Site API: ${siteId} ${url}, body: ${JSON.stringify(body)}`);
22
+ const authorization = await getSiteAccessToken(siteId);
23
+ try {
24
+ const response = await fetch(url, {
25
+ method,
26
+ headers: {
27
+ Authorization: authorization,
28
+ ...(body ? { 'Content-Type': 'application/json' } : {})
29
+ },
30
+ body: method === 'GET' ? undefined : body
31
+ });
32
+ const responseData = await handleWixAPIResponse(response);
33
+ return {
34
+ content: [
35
+ {
36
+ type: 'text',
37
+ text: `Wix Site API call successful: ${JSON.stringify(responseData)}`
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ catch (error) {
43
+ logger.error(`Failed to call Wix Site API: ${error}`);
44
+ throw new Error(`Failed to call Wix Site API: ${error}`);
45
+ }
46
+ });
47
+ server.tool('ListWixSites', 'List Wix sites for the current user', async () => {
48
+ const sitesRes = await fetch('https://www.wixapis.com/site-list/v2/sites/query', {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ Accept: 'application/json, text/plain, */*',
53
+ Authorization: await getAccountAccessToken()
54
+ },
55
+ body: JSON.stringify({
56
+ query: {
57
+ cursorPaging: { limit: 50 }
58
+ }
59
+ })
60
+ });
61
+ const result = await sitesRes.json().then(({ sites }) => sites?.map(({ id, displayName }) => ({
62
+ id,
63
+ name: displayName
64
+ })) ?? []);
65
+ return {
66
+ content: [
67
+ {
68
+ type: 'text',
69
+ text: JSON.stringify(result)
70
+ },
71
+ {
72
+ type: 'text',
73
+ text: 'if there is more than one site returned, the user should pick one from a list, list the sites (only name) for the user and ask them to pick one'
74
+ }
75
+ ]
76
+ };
77
+ });
78
+ server.tool('ManageWixSite', `Use account level API in order to create a site, update a site and publish site.
79
+ ALWAYS use "SearchWixRESTDocumentation" to search for the API you should invoke, NEVER GUESS THE SITE API URL`, {
80
+ url: z
81
+ .string()
82
+ .describe('The url of the api to call - ALWAYS get the information from the Wix REST docs DONT GUESS IT, the URL MUST BE ABSOLUTE URL'),
83
+ method: z
84
+ .string()
85
+ .describe('The HTTP method to use for the API call (e.g. GET, POST, PUT, DELETE)'),
86
+ body: z
87
+ .string()
88
+ .optional()
89
+ .describe('A string representing of a valid JSON object to describe the body of the request')
90
+ }, async ({ url, body, method }) => {
91
+ logger.log(`Calling Wix Account level API: ${url}, body: ${JSON.stringify(body)}`);
92
+ const authorization = await getAccountAccessToken();
93
+ try {
94
+ const response = await fetch(url, {
95
+ method,
96
+ headers: {
97
+ Authorization: authorization,
98
+ ...(body ? { 'Content-Type': 'application/json' } : {})
99
+ },
100
+ body: method === 'GET' ? undefined : body
101
+ });
102
+ const responseData = await handleWixAPIResponse(response);
103
+ return {
104
+ content: [
105
+ {
106
+ type: 'text',
107
+ text: `Wix Account API call successful: ${JSON.stringify(responseData)}`
108
+ }
109
+ ]
110
+ };
111
+ }
112
+ catch (error) {
113
+ logger.error(`Failed to call Wix Account API: ${error}`);
114
+ throw new Error(`Failed to call Wix Account API: ${error}`);
115
+ }
116
+ });
117
+ }
package/build/bin.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import './sentry.js';
2
+ declare const PUBLIC_TOOLS: readonly ["WDS", "REST", "SDK", "BUILD_APPS", "WIX_HEADLESS", "BUSINESS_SOLUTIONS"];
3
+ declare const EXPERIMENTAL_TOOLS: readonly ["WIX_API", "CLI_COMMAND"];
4
+ type Tool = (typeof PUBLIC_TOOLS)[number] | (typeof EXPERIMENTAL_TOOLS)[number];
5
+ export declare const DEFAULT_TOOLS: Tool[];
6
+ export {};
package/build/bin.js ADDED
@@ -0,0 +1,97 @@
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 PUBLIC_TOOLS = [...VALID_DOCS_TOOLS];
10
+ const EXPERIMENTAL_TOOLS = ['WIX_API', 'CLI_COMMAND'];
11
+ export const DEFAULT_TOOLS = [...VALID_DOCS_TOOLS];
12
+ const parsedArgs = minimist(process.argv.slice(2));
13
+ function parseExperimentalArg() {
14
+ const experimentalArg = parsedArgs['experimental'];
15
+ if (!experimentalArg)
16
+ return [];
17
+ return experimentalArg
18
+ .split(',')
19
+ .map((t) => t.trim())
20
+ .filter((tool) => EXPERIMENTAL_TOOLS.includes(tool));
21
+ }
22
+ function parseToolsArg() {
23
+ const toolsArg = parsedArgs['tools'];
24
+ const experimentalTools = parseExperimentalArg();
25
+ if (!toolsArg) {
26
+ // When no tools specified, return both default and experimental tools
27
+ return [...DEFAULT_TOOLS, ...experimentalTools];
28
+ }
29
+ const requestedTools = toolsArg.split(',').map((t) => t.trim());
30
+ const tools = [
31
+ // Include valid non-experimental tools
32
+ ...requestedTools.filter((tool) => PUBLIC_TOOLS.includes(tool)),
33
+ // Include enabled experimental tools
34
+ ...experimentalTools
35
+ ];
36
+ // Warn about enabled experimental tools
37
+ tools.forEach((tool) => {
38
+ if (EXPERIMENTAL_TOOLS.includes(tool)) {
39
+ logger.log(`Warning: ${tool} is an experimental tool and may have limited functionality or breaking changes`);
40
+ }
41
+ });
42
+ return tools;
43
+ }
44
+ const loggerType = parsedArgs['logger'] || 'mcp';
45
+ if (loggerType === 'file') {
46
+ attachFileLogger();
47
+ }
48
+ else {
49
+ // Initially we log to stderr, because MCP server is not connected yet
50
+ // When the server is connected, we attach the MCP server logger
51
+ attachStdErrLogger();
52
+ }
53
+ logger.log('--------------------------------');
54
+ logger.log('starting WIX MCP server');
55
+ logger.log('--------------------------------');
56
+ const server = new WixMcpServer({
57
+ name: 'wix-mcp-server',
58
+ version: '1.0.0'
59
+ }, {
60
+ capabilities: {
61
+ tools: {},
62
+ prompts: {},
63
+ logging: {},
64
+ resources: {}
65
+ }
66
+ });
67
+ const activeTools = parseToolsArg();
68
+ logger.log('Active tools:', activeTools);
69
+ const docsTools = activeTools.filter((tool) => VALID_DOCS_TOOLS.includes(tool));
70
+ if (docsTools.length > 0) {
71
+ logger.log('Adding docs tools:', docsTools);
72
+ addDocsTools(server, docsTools);
73
+ }
74
+ const cliTools = activeTools.filter((tool) => VALID_CLI_TOOLS.includes(tool));
75
+ if (cliTools.length > 0) {
76
+ logger.log('Adding cli tools:', cliTools);
77
+ addCliTools(server, cliTools);
78
+ }
79
+ try {
80
+ const portals = parsedArgs['portals']?.split(',') || [];
81
+ if (portals.length > 0) {
82
+ logger.log('Adding docs resources for portals:', portals);
83
+ await addDocsResources(server, portals);
84
+ }
85
+ }
86
+ catch (error) {
87
+ logger.error('Error adding docs resources:', error);
88
+ }
89
+ logger.log('Starting server');
90
+ const transport = new StdioServerTransport();
91
+ logger.log('Connecting to transport');
92
+ await server.connect(transport);
93
+ logger.log('Transport connected');
94
+ if (loggerType === 'mcp') {
95
+ // From now on, we log to the MCP server
96
+ attachMcpServerLogger(server);
97
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const VALID_CLI_TOOLS: readonly ["WIX_API", "CLI_COMMAND", "CLI_COMMAND_INTERACTIVE_MODE"];
3
+ export type CLITool = (typeof VALID_CLI_TOOLS)[number];
4
+ export declare function addCliTools(server: McpServer, allowedTools?: CLITool[]): void;
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
- import { getCliAuthTokenForSiteId, getSiteIdFromCliAppConfig, handleWixAPIResponse } from './utils.js';
2
+ import { getCliAuthTokenForSiteId, getSiteIdFromCliAppConfig } from './utils.js';
3
3
  import { logger } from '../logger.js';
4
4
  import { execa } from 'execa';
5
5
  import { mockInteractiveGenerateCommandTool } from '../interactive-command-tools/interactive-command-utils.js';
6
+ import { handleWixAPIResponse } from '../tool-utils.js';
6
7
  export const VALID_CLI_TOOLS = [
7
8
  'WIX_API',
8
9
  'CLI_COMMAND',
@@ -0,0 +1,2 @@
1
+ export declare function getCliAuthTokenForSiteId(siteIdOrAccountsFileName: string): string;
2
+ export declare function getSiteIdFromCliAppConfig(appPath: string): any;
@@ -10,22 +10,3 @@ export function getSiteIdFromCliAppConfig(appPath) {
10
10
  const appConfig = JSON.parse(readFileSync(path.join(appPath, '.wix/app.config.json'), 'utf8'));
11
11
  return appConfig.siteId;
12
12
  }
13
- const safeParseJSON = (text) => {
14
- try {
15
- return JSON.parse(text);
16
- }
17
- catch {
18
- return text;
19
- }
20
- };
21
- export const handleWixAPIResponse = async (response) => {
22
- const responseText = await response.text();
23
- const responseData = safeParseJSON(responseText);
24
- if (!response.ok) {
25
- const errorDetails = typeof responseData === 'object'
26
- ? JSON.stringify(responseData)
27
- : responseData;
28
- throw new Error(`Failed to call Wix API: ${response.status} ${response.statusText}. ${errorDetails}`);
29
- }
30
- return responseData;
31
- };
@@ -0,0 +1,4 @@
1
+ import type { WixMcpServer } from '../wix-mcp-server.js';
2
+ export declare const VALID_DOCS_TOOLS: readonly ["WDS", "REST", "SDK", "BUILD_APPS", "WIX_HEADLESS", "BUSINESS_SOLUTIONS"];
3
+ export type DocsTool = (typeof VALID_DOCS_TOOLS)[number];
4
+ export declare const addDocsTools: (server: WixMcpServer, allowedTools?: DocsTool[]) => void;
@@ -7,14 +7,16 @@ export const VALID_DOCS_TOOLS = [
7
7
  'REST',
8
8
  'SDK',
9
9
  'BUILD_APPS',
10
- 'WIX_HEADLESS'
10
+ 'WIX_HEADLESS',
11
+ 'BUSINESS_SOLUTIONS'
11
12
  ];
12
13
  export const addDocsTools = (server, allowedTools = [
13
14
  'WDS',
14
15
  'REST',
15
16
  'SDK',
16
17
  'BUILD_APPS',
17
- 'WIX_HEADLESS'
18
+ 'WIX_HEADLESS',
19
+ 'BUSINESS_SOLUTIONS'
18
20
  ]) => {
19
21
  // WDS Documentation
20
22
  if (allowedTools.includes('WDS')) {
@@ -33,13 +35,17 @@ export const addDocsTools = (server, allowedTools = [
33
35
  .min(1)
34
36
  .max(15)
35
37
  .optional()
36
- .default(5)
38
+ .default(10)
37
39
  }, async ({ searchTerm, maxResults }, { panorama }) => {
38
40
  try {
39
41
  logger.log(`Searching for ${searchTerm} in Wix WDS`);
40
- const result = await runSemanticSearchAndFormat('WDS', {
41
- searchTerm: searchTerm
42
- }, Math.max(1, Math.min(maxResults ?? 5, 15)));
42
+ const result = await runSemanticSearchAndFormat({
43
+ toolName: 'WDS',
44
+ toolParams: {
45
+ searchTerm: searchTerm
46
+ },
47
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15))
48
+ });
43
49
  return {
44
50
  content: [
45
51
  {
@@ -82,18 +88,27 @@ export const addDocsTools = (server, allowedTools = [
82
88
  .min(1)
83
89
  .max(15)
84
90
  .optional()
85
- .default(5)
91
+ .default(10)
86
92
  }, async ({ searchTerm, maxResults }, { panorama }) => {
87
93
  try {
88
94
  logger.log(`Searching for ${searchTerm} in Wix REST API`);
89
- const result = await runSemanticSearchAndFormat('REST', {
90
- searchTerm: searchTerm
91
- }, Math.max(1, Math.min(maxResults ?? 5, 15)));
95
+ const result = await runSemanticSearchAndFormat({
96
+ toolName: 'REST',
97
+ toolParams: {
98
+ searchTerm: searchTerm
99
+ },
100
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15)),
101
+ linesInEachResult: 15
102
+ });
92
103
  return {
93
104
  content: [
94
105
  {
95
106
  type: 'text',
96
107
  text: result
108
+ },
109
+ {
110
+ type: 'text',
111
+ text: 'Next, you MUST use the ReadFullDocsArticle tool if you want to read the full documentation for a specific article or method.'
97
112
  }
98
113
  ]
99
114
  };
@@ -114,6 +129,58 @@ export const addDocsTools = (server, allowedTools = [
114
129
  }
115
130
  });
116
131
  }
132
+ if (allowedTools.includes('BUSINESS_SOLUTIONS')) {
133
+ server.tool('SearchWixSampleFlowsDocumentation', [
134
+ 'Searches the official Wix Business solutions Sample flows documentations.',
135
+ 'This tool provides guidance when there is a need to perform several actions over the Wix platform, and a sample flow is needed',
136
+ 'For example, various ways to setup a new business, or an end to end user of user flow',
137
+ 'Once the information is retrieved you can use Wix REST API docs in order to query the specific APIs you need to use in each step'
138
+ ].join('\n'), {
139
+ searchTerm: z
140
+ .string()
141
+ .describe('The search term for the required sample flow (e.g. "setup a new business")'),
142
+ maxResults: z
143
+ .number()
144
+ .describe('The maximum number of results to return, default is 5, max is 15')
145
+ .min(1)
146
+ .max(15)
147
+ .optional()
148
+ .default(5)
149
+ }, async ({ searchTerm, maxResults }, { panorama }) => {
150
+ try {
151
+ logger.log(`Searching for ${searchTerm} in Wix Sample Flows`);
152
+ const result = await runSemanticSearchAndFormat({
153
+ toolName: 'BUSINESS_SOLUTIONS',
154
+ toolParams: {
155
+ searchTerm: 'Sample Flow for: ' + searchTerm
156
+ },
157
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15))
158
+ });
159
+ return {
160
+ content: [
161
+ {
162
+ type: 'text',
163
+ text: result
164
+ }
165
+ ]
166
+ };
167
+ }
168
+ catch (error) {
169
+ panorama.errorMonitor().reportError(error);
170
+ captureException(error, {
171
+ tags: {
172
+ componentId: 'SearchWixSampleFlowsDocumentation',
173
+ toolName: 'SearchWixSampleFlowsDocumentation'
174
+ }
175
+ });
176
+ logger.error(`Error searching for ${searchTerm} in Wix Sample Flows: ${error}`);
177
+ return {
178
+ isError: true,
179
+ content: [{ type: 'text', text: 'Error: ' + error.message }]
180
+ };
181
+ }
182
+ });
183
+ }
117
184
  // SDK Documentation
118
185
  if (allowedTools.includes('SDK')) {
119
186
  server.tool('SearchWixSDKDocumentation', [
@@ -138,9 +205,14 @@ export const addDocsTools = (server, allowedTools = [
138
205
  panorama
139
206
  .transaction('SearchWixSDKDocumentation')
140
207
  .start({ maxResults });
141
- const result = await runSemanticSearchAndFormat('SDK', {
142
- searchTerm: searchTerm
143
- }, Math.max(1, Math.min(maxResults ?? 5, 15)));
208
+ const result = await runSemanticSearchAndFormat({
209
+ toolName: 'SDK',
210
+ toolParams: {
211
+ searchTerm: searchTerm
212
+ },
213
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15)),
214
+ linesInEachResult: 15
215
+ });
144
216
  panorama
145
217
  .transaction('SearchWixSDKDocumentation')
146
218
  .finish({ maxResults });
@@ -149,6 +221,10 @@ export const addDocsTools = (server, allowedTools = [
149
221
  {
150
222
  type: 'text',
151
223
  text: result
224
+ },
225
+ {
226
+ type: 'text',
227
+ text: 'Next, you MUST use ReadFullDocsArticle tool if you want to read full documentation for a specific article or method.'
152
228
  }
153
229
  ]
154
230
  };
@@ -190,9 +266,13 @@ export const addDocsTools = (server, allowedTools = [
190
266
  }, async ({ searchTerm, maxResults }, { panorama }) => {
191
267
  try {
192
268
  logger.log(`Searching for ${searchTerm} in Build Apps`);
193
- const result = await runSemanticSearchAndFormat('BUILD_APPS', {
194
- searchTerm: `wix cli docs for ${searchTerm}`
195
- }, Math.max(1, Math.min(maxResults ?? 5, 15)));
269
+ const result = await runSemanticSearchAndFormat({
270
+ toolName: 'BUILD_APPS',
271
+ toolParams: {
272
+ searchTerm: `wix cli docs for ${searchTerm}`
273
+ },
274
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15))
275
+ });
196
276
  return {
197
277
  content: [
198
278
  {
@@ -238,9 +318,13 @@ export const addDocsTools = (server, allowedTools = [
238
318
  }, async ({ searchTerm, maxResults }, { panorama }) => {
239
319
  try {
240
320
  logger.log(`Searching for ${searchTerm} in Headless`);
241
- const result = await runSemanticSearchAndFormat('WIX_HEADLESS', {
242
- searchTerm: searchTerm
243
- }, Math.max(1, Math.min(maxResults ?? 5, 15)));
321
+ const result = await runSemanticSearchAndFormat({
322
+ toolName: 'WIX_HEADLESS',
323
+ toolParams: {
324
+ searchTerm: searchTerm
325
+ },
326
+ maxResults: Math.max(1, Math.min(maxResults ?? 5, 15))
327
+ });
244
328
  return {
245
329
  content: [
246
330
  {
@@ -265,4 +349,91 @@ export const addDocsTools = (server, allowedTools = [
265
349
  }
266
350
  });
267
351
  }
352
+ server.tool('ReadFullDocsArticle', [
353
+ 'Fetches the full Wix docs article or method article.',
354
+ 'Use this tool when you read a summary of a docs article or method article, you have the docs url and want to read the full article.'
355
+ ].join('\n'), {
356
+ articleUrl: z
357
+ .string()
358
+ .describe('The URL of the docs article or method article to fetch. Should be something like https://dev.wix.com/docs/.../.../...')
359
+ }, async ({ articleUrl }) => {
360
+ try {
361
+ const articleContent = await fetchArticleContent(articleUrl, 'article');
362
+ if (!articleContent) {
363
+ throw new Error('Article content is empty');
364
+ }
365
+ return {
366
+ content: [
367
+ {
368
+ type: 'text',
369
+ text: articleContent
370
+ },
371
+ {
372
+ type: 'text',
373
+ text: [
374
+ '---',
375
+ 'Next, if you are dealing with a method - you MUST call ReadFullDocsMethodSchema tool - it will give you the full request schema for the method.'
376
+ ].join('\n')
377
+ }
378
+ ]
379
+ };
380
+ }
381
+ catch (error) {
382
+ logger.error(`Error fetching article content or method schema for ${articleUrl}: ${error}`);
383
+ return {
384
+ content: [
385
+ {
386
+ type: 'text',
387
+ text: 'Could not fetch article content, you should try completing the task without it.'
388
+ }
389
+ ]
390
+ };
391
+ }
392
+ });
393
+ server.tool('ReadFullDocsMethodSchema', [
394
+ 'Fetches the full method schema for a given method. Always use it before calling the method.',
395
+ 'This will give you the entire request/response schema with all the fields and their descriptions.'
396
+ ].join('\n'), {
397
+ articleUrl: z
398
+ .string()
399
+ .describe('The URL of the documentation to fetch. Should be something like https://dev.wix.com/docs/.../.../...')
400
+ }, async ({ articleUrl }) => {
401
+ try {
402
+ const articleContent = await fetchArticleContent(articleUrl, 'methodSchema');
403
+ if (!articleContent) {
404
+ throw new Error('Method schema is empty');
405
+ }
406
+ return {
407
+ content: [
408
+ {
409
+ type: 'text',
410
+ text: articleContent
411
+ }
412
+ ]
413
+ };
414
+ }
415
+ catch (error) {
416
+ logger.error(`Error fetching method schema for ${articleUrl}: ${error}`);
417
+ return {
418
+ content: [
419
+ {
420
+ type: 'text',
421
+ text: 'Could not fetch method schema, you should try completing the task without it.'
422
+ }
423
+ ]
424
+ };
425
+ }
426
+ });
268
427
  };
428
+ async function fetchArticleContent(articleUrl, mode) {
429
+ // https://dev.wix.com/digor/api/get-article-content?articleUrl=https://dev.wix.com/docs/rest/business-solutions/blog/draft-posts/create-draft-post&schema=true
430
+ const url = new URL(`https://dev.wix.com/digor/api/get-article-content`);
431
+ url.searchParams.set('articleUrl', articleUrl);
432
+ const schema = mode === 'methodSchema' ? 'true' : 'false';
433
+ url.searchParams.set('schema', schema);
434
+ logger.log(`Fetching resource from docs ${url.toString()}`);
435
+ const response = await fetch(url.toString());
436
+ const data = await response.json();
437
+ logger.log(`Fetched resource from docs: ${data.articleContent}`);
438
+ return data.articleContent;
439
+ }
@@ -0,0 +1 @@
1
+ export declare 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````";