@convertcom/mcp-server 0.0.1-beta.10

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.
Files changed (54) hide show
  1. package/.assets/logo-dark.svg +20 -0
  2. package/.assets/logo-light.svg +20 -0
  3. package/README.md +277 -0
  4. package/build/api-config.d.ts +4 -0
  5. package/build/api-config.js +139 -0
  6. package/build/api-config.js.map +1 -0
  7. package/build/debug-log.d.ts +6 -0
  8. package/build/debug-log.js +46 -0
  9. package/build/debug-log.js.map +1 -0
  10. package/build/executor.d.ts +13 -0
  11. package/build/executor.js +783 -0
  12. package/build/executor.js.map +1 -0
  13. package/build/index.d.ts +10 -0
  14. package/build/index.js +453 -0
  15. package/build/index.js.map +1 -0
  16. package/build/kb-hybrid.d.ts +20 -0
  17. package/build/kb-hybrid.js +70 -0
  18. package/build/kb-hybrid.js.map +1 -0
  19. package/build/kb-search.d.ts +18 -0
  20. package/build/kb-search.js +77 -0
  21. package/build/kb-search.js.map +1 -0
  22. package/build/kb-semantic.d.ts +20 -0
  23. package/build/kb-semantic.js +135 -0
  24. package/build/kb-semantic.js.map +1 -0
  25. package/build/namespaced.d.ts +10 -0
  26. package/build/namespaced.js +927 -0
  27. package/build/namespaced.js.map +1 -0
  28. package/build/operator.d.ts +17 -0
  29. package/build/operator.js +1320 -0
  30. package/build/operator.js.map +1 -0
  31. package/build/prompts.d.ts +13 -0
  32. package/build/prompts.js +201 -0
  33. package/build/prompts.js.map +1 -0
  34. package/build/shared.d.ts +6 -0
  35. package/build/shared.js +14 -0
  36. package/build/shared.js.map +1 -0
  37. package/build/tools.d.ts +19 -0
  38. package/build/tools.js +1293 -0
  39. package/build/tools.js.map +1 -0
  40. package/data/convert-kb.json +28882 -0
  41. package/data/kb-vectors/0/embedding.index.3.proxima +0 -0
  42. package/data/kb-vectors/0/scalar.0.ipc +0 -0
  43. package/data/kb-vectors/LOCK +0 -0
  44. package/data/kb-vectors/del.0 +0 -0
  45. package/data/kb-vectors/idmap.0/000008.sst +0 -0
  46. package/data/kb-vectors/idmap.0/CURRENT +1 -0
  47. package/data/kb-vectors/idmap.0/IDENTITY +1 -0
  48. package/data/kb-vectors/idmap.0/LOCK +0 -0
  49. package/data/kb-vectors/idmap.0/LOG +0 -0
  50. package/data/kb-vectors/idmap.0/MANIFEST-000005 +0 -0
  51. package/data/kb-vectors/idmap.0/OPTIONS-000007 +199 -0
  52. package/data/kb-vectors/manifest.3 +9 -0
  53. package/package.json +71 -0
  54. package/server.json +17 -0
@@ -0,0 +1,783 @@
1
+ import axios from 'axios';
2
+ import { Ajv2020 } from 'ajv/dist/2020.js';
3
+ import { toolDefinitionMap } from './tools.js';
4
+ import { API_BASE_URL } from './api-config.js';
5
+ import { JSON_SCHEMA_DRAFT_2020_12, pathParamsFromTemplate } from './shared.js';
6
+ const MAX_API_ERROR_DETAIL_LEN = 2000;
7
+ const MAX_SCHEMA_ERRORS_TO_SHOW = 8;
8
+ const validatorCache = new Map();
9
+ const ajv = new Ajv2020({
10
+ allErrors: true,
11
+ strict: false,
12
+ allowUnionTypes: true,
13
+ validateFormats: false,
14
+ });
15
+ function resolvePathTemplate(pathTemplate, args) {
16
+ const params = pathParamsFromTemplate(pathTemplate);
17
+ let resolvedPath = pathTemplate;
18
+ const missing = [];
19
+ for (const name of params) {
20
+ const value = args[name];
21
+ if (typeof value === 'undefined' || value === null) {
22
+ missing.push(name);
23
+ continue;
24
+ }
25
+ resolvedPath = resolvedPath.replace(`{${name}}`, encodeURIComponent(String(value)));
26
+ }
27
+ return { resolvedPath, missing };
28
+ }
29
+ export async function executeToolByName(toolName, toolArgs) {
30
+ const toolDefinition = toolDefinitionMap.get(toolName);
31
+ if (!toolDefinition) {
32
+ return {
33
+ isError: true,
34
+ content: [{ type: 'text', text: 'Error: Unknown tool requested: ' + toolName }],
35
+ structuredContent: { error: `Unknown tool: ${toolName}` },
36
+ };
37
+ }
38
+ return await executeApiTool(toolName, toolDefinition, toolArgs);
39
+ }
40
+ async function executeApiTool(toolName, definition, toolArgs) {
41
+ let uploadScreenshotReadbackUrl = null;
42
+ try {
43
+ const argsToValidate = (typeof toolArgs === 'object' && toolArgs !== null) ? toolArgs : {};
44
+ const validation = validateToolArgs(toolName, definition.inputSchema, argsToValidate);
45
+ if (!validation.ok) {
46
+ return {
47
+ isError: true,
48
+ content: [{ type: 'text', text: validation.message }],
49
+ structuredContent: {
50
+ error: 'InvalidArguments',
51
+ tool: toolName,
52
+ issues: validation.errors,
53
+ },
54
+ };
55
+ }
56
+ const validatedArgs = argsToValidate;
57
+ let urlPath = definition.pathTemplate;
58
+ const queryParams = {};
59
+ const headers = { 'Accept': 'application/json' };
60
+ let requestBodyData = undefined;
61
+ // Apply basic authentication (Bearer token)
62
+ const bearerToken = process.env.BEARER_TOKEN || process.env.API_TOKEN;
63
+ if (bearerToken) {
64
+ headers['authorization'] = `Bearer ${bearerToken}`;
65
+ }
66
+ definition.executionParameters.forEach((param) => {
67
+ const value = validatedArgs[param.name];
68
+ if (typeof value !== 'undefined' && value !== null) {
69
+ if (param.in === 'path') {
70
+ urlPath = urlPath.replace(`{${param.name}}`, encodeURIComponent(String(value)));
71
+ }
72
+ else if (param.in === 'query') {
73
+ // Always keep arrays as arrays - we'll serialize them with bracket notation later
74
+ // Note: OpenAPI spec says explode=false but Convert API requires bracket notation (explode=true behavior)
75
+ queryParams[param.name] = value;
76
+ }
77
+ else if (param.in === 'header') {
78
+ headers[param.name.toLowerCase()] = String(value);
79
+ }
80
+ }
81
+ });
82
+ const resolved = resolvePathTemplate(urlPath, validatedArgs);
83
+ urlPath = resolved.resolvedPath;
84
+ if (resolved.missing.length) {
85
+ const availableArgKeys = Object.keys(validatedArgs).sort().join(', ') || '(none)';
86
+ throw new Error(`Failed to resolve path parameters: missing ${resolved.missing.join(', ')} for template ${definition.pathTemplate}. Provided keys: ${availableArgKeys}`);
87
+ }
88
+ // Build full URL with query string directly - don't rely on axios params serialization
89
+ let fullUrl = API_BASE_URL + urlPath;
90
+ if (Object.keys(queryParams).length > 0) {
91
+ // Serialize query params with bracket notation for arrays
92
+ const parts = [];
93
+ for (const [key, value] of Object.entries(queryParams)) {
94
+ if (Array.isArray(value)) {
95
+ value.forEach((v) => {
96
+ parts.push(`${key}[]=${encodeURIComponent(String(v))}`);
97
+ });
98
+ }
99
+ else {
100
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
101
+ }
102
+ }
103
+ fullUrl = fullUrl + '?' + parts.join('&');
104
+ }
105
+ if (definition.requestBodyContentType && typeof validatedArgs['requestBody'] !== 'undefined') {
106
+ if (isMultipartContentType(definition.requestBodyContentType)) {
107
+ requestBodyData = buildMultipartFormData(validatedArgs['requestBody']);
108
+ }
109
+ else {
110
+ requestBodyData = validatedArgs['requestBody'];
111
+ headers['content-type'] = definition.requestBodyContentType;
112
+ }
113
+ }
114
+ const config = {
115
+ method: (definition.method || 'get').toUpperCase(),
116
+ url: fullUrl,
117
+ // Don't pass params - query string is already in URL
118
+ headers,
119
+ ...(requestBodyData !== undefined && { data: requestBodyData }),
120
+ };
121
+ if (toolName === 'uploadexperiencevariationscreenshot') {
122
+ uploadScreenshotReadbackUrl = fullUrl;
123
+ }
124
+ const response = await axios(config);
125
+ let responseText = '';
126
+ const contentType = response.headers['content-type']?.toLowerCase() || '';
127
+ const isJsonResponse = contentType.includes('application/json') && typeof response.data === 'object' && response.data !== null;
128
+ if (isJsonResponse) {
129
+ try {
130
+ responseText = JSON.stringify(response.data, null, 2);
131
+ }
132
+ catch {
133
+ responseText = '[Stringify Error]';
134
+ }
135
+ }
136
+ else if (typeof response.data === 'string') {
137
+ responseText = response.data;
138
+ }
139
+ else if (response.data !== undefined && response.data !== null) {
140
+ responseText = String(response.data);
141
+ }
142
+ else {
143
+ responseText = `(Status: ${response.status} - No body content)`;
144
+ }
145
+ return {
146
+ content: [{ type: 'text', text: `API Response (Status: ${response.status}):\n${responseText}` }],
147
+ structuredContent: isJsonResponse
148
+ ? { status: response.status, data: response.data }
149
+ : { status: response.status },
150
+ };
151
+ }
152
+ catch (error) {
153
+ let errorMessage;
154
+ if (axios.isAxiosError(error)) {
155
+ const recovered = await recoverUploadedScreenshot(toolName, error, uploadScreenshotReadbackUrl);
156
+ if (recovered)
157
+ return recovered;
158
+ errorMessage = formatApiError(error);
159
+ }
160
+ else if (error instanceof Error) {
161
+ errorMessage = error.message;
162
+ }
163
+ else {
164
+ errorMessage = `Unexpected error: ${String(error)}`;
165
+ }
166
+ console.error(`Error during execution: ${errorMessage}`);
167
+ return {
168
+ isError: true,
169
+ content: [{ type: 'text', text: errorMessage }],
170
+ structuredContent: { error: errorMessage },
171
+ };
172
+ }
173
+ }
174
+ export async function recoverUploadedScreenshot(toolName, error, readbackUrl) {
175
+ if (toolName !== 'uploadexperiencevariationscreenshot')
176
+ return null;
177
+ if (error.response?.status !== 500 || !readbackUrl)
178
+ return null;
179
+ for (let attempt = 0; attempt < 4; attempt += 1) {
180
+ if (attempt > 0)
181
+ await delay(250 * attempt);
182
+ try {
183
+ const response = await axios.get(readbackUrl, { headers: { 'Accept': 'application/json' } });
184
+ const data = response.data;
185
+ if (!isPlainObject(data) || typeof data.data !== 'string' || data.data.length === 0) {
186
+ continue;
187
+ }
188
+ const recoveredData = {
189
+ ...data,
190
+ warning: 'UploadReturned500ButReadbackSucceeded',
191
+ recovered: true,
192
+ };
193
+ return {
194
+ content: [{
195
+ type: 'text',
196
+ text: `API Response (Status: 200):\n${JSON.stringify(recoveredData, null, 2)}`,
197
+ }],
198
+ structuredContent: {
199
+ status: 200,
200
+ data: recoveredData,
201
+ warning: 'UploadReturned500ButReadbackSucceeded',
202
+ recovered: true,
203
+ },
204
+ };
205
+ }
206
+ catch {
207
+ // Keep the original upload failure if readback cannot verify persistence.
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+ function delay(ms) {
213
+ return new Promise((resolve) => setTimeout(resolve, ms));
214
+ }
215
+ function isMultipartContentType(contentType) {
216
+ return contentType.toLowerCase().startsWith('multipart/form-data');
217
+ }
218
+ function buildMultipartFormData(requestBody) {
219
+ const form = new FormData();
220
+ const fields = multipartFieldsFromBody(requestBody);
221
+ for (const [field, value] of Object.entries(fields)) {
222
+ appendMultipartField(form, field, value);
223
+ }
224
+ return form;
225
+ }
226
+ function multipartFieldsFromBody(requestBody) {
227
+ if (typeof requestBody === 'string') {
228
+ return { image: requestBody };
229
+ }
230
+ if (requestBody && typeof requestBody === 'object' && !Array.isArray(requestBody)) {
231
+ return requestBody;
232
+ }
233
+ throw new Error('Multipart requestBody must be an object of form fields or a data URL/base64 string for the image field.');
234
+ }
235
+ function appendMultipartField(form, field, value) {
236
+ const file = multipartFileFromValue(field, value);
237
+ if (file) {
238
+ form.append(field, file.blob, file.filename);
239
+ return;
240
+ }
241
+ if (typeof value === 'undefined' || value === null)
242
+ return;
243
+ if (typeof value === 'object') {
244
+ form.append(field, JSON.stringify(value));
245
+ return;
246
+ }
247
+ form.append(field, String(value));
248
+ }
249
+ function multipartFileFromValue(field, value) {
250
+ if (typeof value === 'string') {
251
+ const parsed = binaryPayloadFromString(value, defaultMimeTypeForField(field));
252
+ if (!parsed)
253
+ return null;
254
+ return {
255
+ blob: new Blob([blobPartFromBuffer(parsed.buffer)], { type: parsed.mimeType }),
256
+ filename: defaultFilenameForField(field, parsed.mimeType),
257
+ };
258
+ }
259
+ if (!value || typeof value !== 'object' || Array.isArray(value))
260
+ return null;
261
+ const obj = value;
262
+ const raw = firstString(obj.data_url, obj.dataUrl, obj.base64, obj.content, obj.data, obj.value);
263
+ if (!raw)
264
+ return null;
265
+ const parsed = binaryPayloadFromString(raw, firstString(obj.content_type, obj.contentType, obj.mime_type, obj.mimeType) ?? defaultMimeTypeForField(field));
266
+ if (!parsed)
267
+ return null;
268
+ return {
269
+ blob: new Blob([blobPartFromBuffer(parsed.buffer)], { type: parsed.mimeType }),
270
+ filename: firstString(obj.filename, obj.name) ?? defaultFilenameForField(field, parsed.mimeType),
271
+ };
272
+ }
273
+ function blobPartFromBuffer(buffer) {
274
+ const bytes = new Uint8Array(buffer.byteLength);
275
+ bytes.set(buffer);
276
+ return bytes;
277
+ }
278
+ function binaryPayloadFromString(value, fallbackMimeType) {
279
+ const trimmed = value.trim();
280
+ const dataUrl = trimmed.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
281
+ if (dataUrl) {
282
+ const mimeType = dataUrl[1] || fallbackMimeType;
283
+ const isBase64 = dataUrl[2] === ';base64';
284
+ const payload = dataUrl[3] || '';
285
+ return {
286
+ buffer: isBase64 ? Buffer.from(payload, 'base64') : Buffer.from(decodeURIComponent(payload)),
287
+ mimeType,
288
+ };
289
+ }
290
+ if (looksLikeBase64(trimmed)) {
291
+ return { buffer: Buffer.from(trimmed.replace(/\s+/g, ''), 'base64'), mimeType: fallbackMimeType };
292
+ }
293
+ return null;
294
+ }
295
+ function looksLikeBase64(value) {
296
+ if (value.length < 16 || value.length % 4 !== 0)
297
+ return false;
298
+ return /^[A-Za-z0-9+/]+={0,2}$/.test(value);
299
+ }
300
+ function firstString(...values) {
301
+ for (const value of values) {
302
+ if (typeof value === 'string' && value.trim())
303
+ return value.trim();
304
+ }
305
+ return null;
306
+ }
307
+ function defaultMimeTypeForField(field) {
308
+ return field.toLowerCase().includes('image') ? 'image/png' : 'application/octet-stream';
309
+ }
310
+ function defaultFilenameForField(field, mimeType) {
311
+ const extension = mimeType.includes('jpeg') ? 'jpg'
312
+ : mimeType.includes('png') ? 'png'
313
+ : mimeType.includes('gif') ? 'gif'
314
+ : mimeType.includes('webp') ? 'webp'
315
+ : mimeType.includes('csv') ? 'csv'
316
+ : 'bin';
317
+ return `${field}.${extension}`;
318
+ }
319
+ function formatApiError(error) {
320
+ let message = 'API request failed.';
321
+ if (error.response) {
322
+ const method = error.config?.method?.toUpperCase() || 'UNKNOWN_METHOD';
323
+ const url = error.config?.url || 'UNKNOWN_URL';
324
+ const statusText = error.response.statusText || 'Status text not available';
325
+ const requestId = getRequestId(error.response.headers);
326
+ message = `API Error: ${method} ${url} -> Status ${error.response.status} (${statusText}).`;
327
+ if (requestId) {
328
+ message += ` Request ID: ${requestId}.`;
329
+ }
330
+ const responseDetails = extractApiErrorDetails(error.response.data);
331
+ if (responseDetails) {
332
+ message += ` Details: ${responseDetails}`;
333
+ }
334
+ else {
335
+ message += ' No response body received.';
336
+ }
337
+ }
338
+ else if (error.request) {
339
+ message = 'API Network Error: No response received from server.';
340
+ if (error.code)
341
+ message += ` (Code: ${error.code})`;
342
+ }
343
+ else {
344
+ message = `API Request Setup Error: ${error.message}`;
345
+ }
346
+ return message;
347
+ }
348
+ export function validateToolArgs(toolName, rawSchema, args) {
349
+ const validator = getOrCreateValidator(toolName, rawSchema);
350
+ if (!validator) {
351
+ return {
352
+ ok: false,
353
+ errors: [],
354
+ message: `Internal schema error: could not compile input schema for tool "${toolName}".`,
355
+ };
356
+ }
357
+ const valid = validator(args);
358
+ if (valid)
359
+ return { ok: true };
360
+ const errors = (validator.errors || []);
361
+ const issueSummary = summarizeSchemaErrors(errors);
362
+ const arrayHint = hasIncludeExpandArrayIssue(errors)
363
+ ? '\nHint: Array parameters (like "include" and "expand") must be arrays, even for single values. Example: {"query":{"include":["goals"],"expand":["goals"]}}.'
364
+ : '';
365
+ return {
366
+ ok: false,
367
+ errors,
368
+ message: `Invalid arguments for tool "${toolName}".\n${issueSummary}${arrayHint}`,
369
+ };
370
+ }
371
+ function getOrCreateValidator(toolName, rawSchema) {
372
+ const cached = validatorCache.get(toolName);
373
+ if (cached)
374
+ return cached;
375
+ const fallbackSchema = {
376
+ $schema: JSON_SCHEMA_DRAFT_2020_12,
377
+ type: 'object',
378
+ additionalProperties: true,
379
+ };
380
+ const normalized = normalizeSchema(rawSchema, toolName);
381
+ const schema = (normalized && typeof normalized === 'object') ? normalized : fallbackSchema;
382
+ if (!schema.$schema)
383
+ schema.$schema = JSON_SCHEMA_DRAFT_2020_12;
384
+ try {
385
+ const validator = ajv.compile(schema);
386
+ validatorCache.set(toolName, validator);
387
+ return validator;
388
+ }
389
+ catch (error) {
390
+ const message = error instanceof Error ? error.message : String(error);
391
+ console.error(`Failed to compile JSON schema for '${toolName}': ${message}`);
392
+ return null;
393
+ }
394
+ }
395
+ function normalizeSchema(input, toolName) {
396
+ const seen = new WeakMap();
397
+ function walk(node, path = '') {
398
+ if (!node || typeof node !== 'object')
399
+ return node;
400
+ if (seen.has(node))
401
+ return seen.get(node);
402
+ if (Array.isArray(node)) {
403
+ const arr = [];
404
+ seen.set(node, arr);
405
+ node.forEach((item, index) => arr.push(walk(item, `${path}/${index}`)));
406
+ return arr;
407
+ }
408
+ const out = {};
409
+ seen.set(node, out);
410
+ for (const [key, value] of Object.entries(node)) {
411
+ out[key] = walk(value, `${path}/${escapeJsonPointer(key)}`);
412
+ }
413
+ if (Array.isArray(out.oneOf) && isExperienceChangeUnionPath(path)) {
414
+ out.oneOf = normalizeExperienceChangeUnion(out.oneOf);
415
+ }
416
+ if (out.nullable === true) {
417
+ const type = out.type;
418
+ if (typeof type === 'string') {
419
+ out.type = [type, 'null'];
420
+ }
421
+ else if (Array.isArray(type)) {
422
+ const typeSet = new Set(type);
423
+ typeSet.add('null');
424
+ out.type = [...typeSet];
425
+ }
426
+ else if (Array.isArray(out.oneOf)) {
427
+ out.oneOf = [...out.oneOf, { type: 'null' }];
428
+ }
429
+ else if (Array.isArray(out.anyOf)) {
430
+ out.anyOf = [...out.anyOf, { type: 'null' }];
431
+ }
432
+ delete out.nullable;
433
+ }
434
+ return out;
435
+ }
436
+ return normalizeToolSpecificSchema(toolName, walk(input));
437
+ }
438
+ function escapeJsonPointer(value) {
439
+ return value.replace(/~/g, '~0').replace(/\//g, '~1');
440
+ }
441
+ function isExperienceChangeUnionPath(path) {
442
+ return path.includes('/properties/changes/items');
443
+ }
444
+ function normalizeExperienceChangeUnion(branches) {
445
+ return branches.map((branch, index) => {
446
+ if (!isAutoPlaceholderSchema(branch))
447
+ return branch;
448
+ const inferredType = inferPlaceholderChangeType(branches, index);
449
+ if (inferredType === 'defaultRedirect')
450
+ return defaultRedirectChangeSchema();
451
+ if (inferredType === 'richStructure')
452
+ return richStructureChangeSchema();
453
+ return branch;
454
+ });
455
+ }
456
+ function isAutoPlaceholderSchema(schema) {
457
+ return isPlainObject(schema)
458
+ && typeof schema.description === 'string'
459
+ && schema.description.includes('auto-placeholder');
460
+ }
461
+ function inferPlaceholderChangeType(branches, index) {
462
+ const previousTypes = collectDiscriminantTypes(branches[index - 1]);
463
+ const nextTypes = collectDiscriminantTypes(branches[index + 1]);
464
+ if (index === 2 && nextTypes.has('customCode'))
465
+ return 'defaultRedirect';
466
+ if (previousTypes.has('defaultRedirect'))
467
+ return 'richStructure';
468
+ if (previousTypes.has('customCode') && nextTypes.has('fullStackFeature'))
469
+ return 'richStructure';
470
+ return null;
471
+ }
472
+ function collectDiscriminantTypes(schema) {
473
+ const types = new Set();
474
+ const visited = new WeakSet();
475
+ function walk(node) {
476
+ if (!node || typeof node !== 'object')
477
+ return;
478
+ if (visited.has(node))
479
+ return;
480
+ visited.add(node);
481
+ if (Array.isArray(node)) {
482
+ node.forEach(walk);
483
+ return;
484
+ }
485
+ const record = node;
486
+ const typeProperty = isPlainObject(record.properties) ? record.properties.type : undefined;
487
+ if (isPlainObject(typeProperty) && Array.isArray(typeProperty.enum)) {
488
+ for (const value of typeProperty.enum) {
489
+ if (typeof value === 'string')
490
+ types.add(value);
491
+ }
492
+ }
493
+ for (const value of Object.values(record))
494
+ walk(value);
495
+ }
496
+ walk(schema);
497
+ return types;
498
+ }
499
+ function defaultRedirectChangeSchema() {
500
+ return {
501
+ type: 'object',
502
+ description: 'Normalized schema for a defaultRedirect split URL change. Replaces a broad generated auto-placeholder so oneOf validation can discriminate by type.',
503
+ properties: {
504
+ type: { type: 'string', enum: ['defaultRedirect'] },
505
+ data: {
506
+ type: 'object',
507
+ additionalProperties: false,
508
+ properties: {
509
+ original_pattern: { type: 'string' },
510
+ variation_pattern: { type: 'string' },
511
+ case_sensitive: { type: 'boolean' },
512
+ },
513
+ required: ['original_pattern', 'variation_pattern'],
514
+ },
515
+ },
516
+ required: ['type', 'data'],
517
+ };
518
+ }
519
+ function richStructureChangeSchema() {
520
+ return {
521
+ type: 'object',
522
+ description: 'Normalized schema for a richStructure visual editor change. Replaces a broad generated auto-placeholder so oneOf validation can discriminate by type.',
523
+ properties: {
524
+ id: { type: 'integer' },
525
+ type: { type: 'string', enum: ['richStructure'] },
526
+ data: { type: 'object', additionalProperties: true },
527
+ },
528
+ required: ['type', 'data'],
529
+ additionalProperties: true,
530
+ };
531
+ }
532
+ function normalizeToolSpecificSchema(toolName, schema) {
533
+ if (!isPlainObject(schema))
534
+ return schema;
535
+ const properties = isPlainObject(schema.properties) ? schema.properties : undefined;
536
+ if (!properties || !Object.prototype.hasOwnProperty.call(properties, 'requestBody'))
537
+ return schema;
538
+ if (toolName === 'createaudience') {
539
+ properties.requestBody = audienceRequestBodySchema(true);
540
+ }
541
+ else if (toolName === 'updateaudience') {
542
+ properties.requestBody = audienceRequestBodySchema(false);
543
+ }
544
+ else if (toolName === 'createlocation') {
545
+ properties.requestBody = locationRequestBodySchema(true);
546
+ }
547
+ else if (toolName === 'updatelocation') {
548
+ properties.requestBody = locationRequestBodySchema(false);
549
+ }
550
+ else if (toolName === 'creategoal') {
551
+ properties.requestBody = goalRequestBodySchema(true);
552
+ }
553
+ else if (toolName === 'updategoal') {
554
+ properties.requestBody = goalRequestBodySchema(false);
555
+ }
556
+ else if (toolName === 'createfeature') {
557
+ properties.requestBody = featureRequestBodySchema(true);
558
+ }
559
+ else if (toolName === 'updatefeature') {
560
+ properties.requestBody = featureRequestBodySchema(false);
561
+ }
562
+ else if (toolName === 'uploadexperiencevariationscreenshot') {
563
+ properties.requestBody = uploadImageNoNameRequestBodySchema();
564
+ }
565
+ return schema;
566
+ }
567
+ function uploadImageNoNameRequestBodySchema() {
568
+ return {
569
+ type: 'object',
570
+ description: 'Normalized multipart image upload payload. Accepts either a raw data URL/base64 string in image or an object with data_url/base64 plus optional filename/content_type metadata.',
571
+ additionalProperties: true,
572
+ properties: {
573
+ image: {
574
+ anyOf: [
575
+ { type: 'string', format: 'binary' },
576
+ {
577
+ type: 'object',
578
+ additionalProperties: true,
579
+ properties: {
580
+ data_url: { type: 'string' },
581
+ dataUrl: { type: 'string' },
582
+ base64: { type: 'string' },
583
+ content: { type: 'string' },
584
+ data: { type: 'string' },
585
+ value: { type: 'string' },
586
+ filename: { type: 'string' },
587
+ name: { type: 'string' },
588
+ content_type: { type: 'string' },
589
+ contentType: { type: 'string' },
590
+ mime_type: { type: 'string' },
591
+ mimeType: { type: 'string' },
592
+ },
593
+ },
594
+ ],
595
+ },
596
+ },
597
+ required: ['image'],
598
+ };
599
+ }
600
+ function audienceRequestBodySchema(isCreate) {
601
+ return {
602
+ type: 'object',
603
+ description: 'Normalized Convert audience payload schema. The generated OpenAPI oneOf currently rejects valid non-null rules for segmentation/permanent/transient audiences, so MCP validates the stable envelope and lets the API validate the rule semantics.',
604
+ additionalProperties: true,
605
+ properties: {
606
+ id: { type: 'integer' },
607
+ name: { type: 'string', maxLength: 100 },
608
+ description: { type: ['string', 'null'], maxLength: 500 },
609
+ status: { type: 'string', enum: ['active', 'archived'] },
610
+ type: { type: 'string', enum: ['segmentation', 'permanent', 'transient'] },
611
+ rules: ruleObjectSchema('Audience rules use nested OR -> AND -> OR_WHEN blocks. Audience rule_type values include visitor_type, country, city, region, device/browser fields, cookies, JS conditions, URL fields for segmentation audiences, and page-tag fields.'),
612
+ selected_default: { type: 'boolean' },
613
+ },
614
+ required: isCreate ? ['name', 'type', 'rules'] : [],
615
+ };
616
+ }
617
+ function locationRequestBodySchema(isCreate) {
618
+ return {
619
+ type: 'object',
620
+ description: 'Normalized Convert location payload schema. The generated OpenAPI schema currently treats non-null rules as invalid, so MCP validates the stable envelope and lets the API validate the rule semantics.',
621
+ additionalProperties: true,
622
+ properties: {
623
+ id: { type: 'integer' },
624
+ name: { type: 'string', maxLength: 100 },
625
+ description: { type: ['string', 'null'], maxLength: 500 },
626
+ status: { type: 'string', enum: ['active', 'archived'] },
627
+ rules: ruleObjectSchema('Location rules use nested OR -> AND -> OR_WHEN blocks, usually with URL/page matching rule types.'),
628
+ trigger: { type: ['object', 'null'], additionalProperties: true },
629
+ selected_default: { type: 'boolean' },
630
+ },
631
+ required: isCreate ? ['name', 'rules'] : [],
632
+ };
633
+ }
634
+ function goalRequestBodySchema(isCreate) {
635
+ return {
636
+ type: 'object',
637
+ description: 'Normalized Convert goal payload schema. The generated OpenAPI oneOf is too strict for real goal variants such as clicks_element, code_trigger, and triggering_rule scoping.',
638
+ additionalProperties: true,
639
+ properties: {
640
+ id: { type: 'integer' },
641
+ name: { type: 'string', maxLength: 100 },
642
+ key: { type: 'string', maxLength: 32 },
643
+ description: { type: ['string', 'null'], maxLength: 500 },
644
+ status: { type: 'string', enum: ['active', 'archived'] },
645
+ type: {
646
+ type: 'string',
647
+ enum: [
648
+ 'advanced',
649
+ 'visits_page',
650
+ 'revenue',
651
+ 'clicks_link',
652
+ 'submits_form',
653
+ 'clicks_element',
654
+ 'dom_interaction',
655
+ 'scroll_percentage',
656
+ 'ga_import',
657
+ 'code_trigger',
658
+ ],
659
+ },
660
+ settings: { type: ['object', 'null'], additionalProperties: true },
661
+ triggering_rule: ruleObjectSchema('Goal triggering rules use nested OR -> AND -> OR_WHEN blocks to scope when the goal can fire.'),
662
+ selected_default: { type: 'boolean' },
663
+ },
664
+ required: isCreate ? ['name', 'type'] : [],
665
+ };
666
+ }
667
+ function featureRequestBodySchema(isCreate) {
668
+ return {
669
+ type: 'object',
670
+ description: 'Normalized Convert feature payload schema. The generated OpenAPI oneOf currently combines a base null default_value with type-specific default_value branches, causing valid boolean/number defaults to fail validation.',
671
+ additionalProperties: true,
672
+ properties: {
673
+ id: { type: 'integer' },
674
+ name: { type: 'string', maxLength: 100 },
675
+ key: { type: 'string', maxLength: 32 },
676
+ description: { type: ['string', 'null'], maxLength: 200 },
677
+ status: { type: 'string', enum: ['active', 'archived'] },
678
+ variables: {
679
+ type: 'array',
680
+ items: {
681
+ type: 'object',
682
+ additionalProperties: true,
683
+ properties: {
684
+ key: { type: 'string', maxLength: 32 },
685
+ type: { type: 'string', enum: ['json', 'string', 'boolean', 'float', 'integer'] },
686
+ default_value: {},
687
+ },
688
+ required: ['key', 'type'],
689
+ },
690
+ },
691
+ },
692
+ required: isCreate ? ['name', 'variables'] : [],
693
+ };
694
+ }
695
+ function ruleObjectSchema(description) {
696
+ return {
697
+ type: ['object', 'null'],
698
+ description,
699
+ additionalProperties: true,
700
+ };
701
+ }
702
+ function isPlainObject(value) {
703
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
704
+ }
705
+ function summarizeSchemaErrors(errors) {
706
+ if (!errors.length)
707
+ return 'Validation failed with no issue details.';
708
+ const lines = errors.slice(0, MAX_SCHEMA_ERRORS_TO_SHOW).map((issue) => {
709
+ return `- ${errorPath(issue)}: ${issue.message || 'Invalid value'}`;
710
+ });
711
+ if (errors.length > MAX_SCHEMA_ERRORS_TO_SHOW) {
712
+ lines.push(`- ... ${errors.length - MAX_SCHEMA_ERRORS_TO_SHOW} more issue(s)`);
713
+ }
714
+ return lines.join('\n');
715
+ }
716
+ function errorPath(issue) {
717
+ const pointerPath = issue.instancePath ? issue.instancePath.slice(1).replace(/\//g, '.') : '(root)';
718
+ if (issue.keyword === 'required') {
719
+ const missing = issue.params.missingProperty;
720
+ if (missing) {
721
+ return pointerPath === '(root)' ? missing : `${pointerPath}.${missing}`;
722
+ }
723
+ }
724
+ return pointerPath;
725
+ }
726
+ function hasIncludeExpandArrayIssue(errors) {
727
+ return errors.some((issue) => {
728
+ const missing = issue.keyword === 'required'
729
+ ? issue.params.missingProperty
730
+ : undefined;
731
+ const requiredPath = missing ? `${issue.instancePath}/${missing}` : issue.instancePath;
732
+ return /\/(include|expand)(\/|$)/.test(requiredPath || '');
733
+ });
734
+ }
735
+ function getRequestId(headers) {
736
+ if (!headers || typeof headers !== 'object')
737
+ return null;
738
+ const candidates = ['x-request-id', 'x-correlation-id', 'request-id'];
739
+ for (const key of candidates) {
740
+ const value = headers[key];
741
+ if (typeof value === 'string' && value.trim())
742
+ return value.trim();
743
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string')
744
+ return value[0].trim();
745
+ }
746
+ return null;
747
+ }
748
+ function truncate(value, maxLen) {
749
+ if (value.length <= maxLen)
750
+ return value;
751
+ return `${value.slice(0, maxLen)}...`;
752
+ }
753
+ function extractApiErrorDetails(responseData) {
754
+ if (typeof responseData === 'undefined' || responseData === null)
755
+ return null;
756
+ if (typeof responseData === 'string')
757
+ return truncate(responseData, MAX_API_ERROR_DETAIL_LEN);
758
+ if (typeof responseData === 'object') {
759
+ const data = responseData;
760
+ const preferredKeys = ['message', 'error', 'errors', 'details', 'validation_errors'];
761
+ for (const key of preferredKeys) {
762
+ const value = data[key];
763
+ if (typeof value === 'string' && value.trim())
764
+ return truncate(value.trim(), MAX_API_ERROR_DETAIL_LEN);
765
+ if (Array.isArray(value) || (value && typeof value === 'object')) {
766
+ try {
767
+ return truncate(JSON.stringify(value), MAX_API_ERROR_DETAIL_LEN);
768
+ }
769
+ catch {
770
+ return '[Could not serialize error details]';
771
+ }
772
+ }
773
+ }
774
+ try {
775
+ return truncate(JSON.stringify(data), MAX_API_ERROR_DETAIL_LEN);
776
+ }
777
+ catch {
778
+ return '[Could not serialize error response]';
779
+ }
780
+ }
781
+ return truncate(String(responseData), MAX_API_ERROR_DETAIL_LEN);
782
+ }
783
+ //# sourceMappingURL=executor.js.map