@qikdev/mcp 6.6.5 → 6.6.8
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/build/src/documentation.d.ts +193 -0
- package/build/src/documentation.d.ts.map +1 -0
- package/build/src/documentation.js +1200 -0
- package/build/src/documentation.js.map +1 -0
- package/build/src/index.d.ts +32 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +1046 -82
- package/build/src/index.js.map +1 -1
- package/package.json +1 -1
package/build/src/index.js
CHANGED
|
@@ -49,6 +49,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } f
|
|
|
49
49
|
import axios from 'axios';
|
|
50
50
|
import FormData from 'form-data';
|
|
51
51
|
import { ConfigManager } from './config.js';
|
|
52
|
+
import { QikDocumentationHelper, QIK_DOCUMENTATION } from './documentation.js';
|
|
52
53
|
// Environment variables
|
|
53
54
|
const QIK_API_URL = process.env.QIK_API_URL || 'https://api.qik.dev';
|
|
54
55
|
const QIK_ACCESS_TOKEN = process.env.QIK_ACCESS_TOKEN;
|
|
@@ -199,7 +200,7 @@ export class QikMCPServer {
|
|
|
199
200
|
}
|
|
200
201
|
return { valid: true, info };
|
|
201
202
|
}
|
|
202
|
-
//
|
|
203
|
+
// Enhanced glossary-driven field analysis methods
|
|
203
204
|
isFieldRequired(field) {
|
|
204
205
|
return field.minimum !== undefined && field.minimum > 0;
|
|
205
206
|
}
|
|
@@ -209,6 +210,369 @@ export class QikMCPServer {
|
|
|
209
210
|
getMinimumArrayLength(field) {
|
|
210
211
|
return this.isFieldArray(field) ? (field.minimum || 0) : 0;
|
|
211
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Analyzes field placement rules directly from glossary structure
|
|
215
|
+
* This is the authoritative source for field placement decisions
|
|
216
|
+
*/
|
|
217
|
+
analyzeFieldPlacementFromGlossary(contentType) {
|
|
218
|
+
const typeInfo = this.glossary[contentType];
|
|
219
|
+
if (!typeInfo) {
|
|
220
|
+
throw new Error(`Content type ${contentType} not found in glossary`);
|
|
221
|
+
}
|
|
222
|
+
// Check if this is a custom definition (has definesType)
|
|
223
|
+
const isCustomDefinition = !!typeInfo.definesType;
|
|
224
|
+
const baseType = typeInfo.definesType || null;
|
|
225
|
+
// Extract fields from the glossary structure
|
|
226
|
+
const fields = typeInfo.fields || [];
|
|
227
|
+
const definedFields = typeInfo.definedFields || [];
|
|
228
|
+
// Create widget mapping for enhanced validation
|
|
229
|
+
const widgetFields = new Map();
|
|
230
|
+
// Process fields for widget information
|
|
231
|
+
const processFieldsForWidgets = (fieldList) => {
|
|
232
|
+
for (const field of fieldList) {
|
|
233
|
+
if (field.widget) {
|
|
234
|
+
widgetFields.set(field.key, field.widget);
|
|
235
|
+
}
|
|
236
|
+
// Process nested fields in groups
|
|
237
|
+
if (field.fields && Array.isArray(field.fields)) {
|
|
238
|
+
processFieldsForWidgets(field.fields);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
processFieldsForWidgets(fields);
|
|
243
|
+
processFieldsForWidgets(definedFields);
|
|
244
|
+
return {
|
|
245
|
+
rootLevelFields: new Set(fields.map(f => f.key)),
|
|
246
|
+
dataObjectFields: new Set(definedFields.map((f) => f.key)),
|
|
247
|
+
baseType,
|
|
248
|
+
isCustomDefinition,
|
|
249
|
+
allFields: fields,
|
|
250
|
+
allDefinedFields: definedFields,
|
|
251
|
+
widgetFields
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Validates field placement based on glossary structure
|
|
256
|
+
*/
|
|
257
|
+
validateFieldPlacementFromGlossary(args, contentType) {
|
|
258
|
+
const analysis = this.analyzeFieldPlacementFromGlossary(contentType);
|
|
259
|
+
const errors = [];
|
|
260
|
+
const warnings = [];
|
|
261
|
+
// Check for fields in data object that should be at root level
|
|
262
|
+
if (args.data && typeof args.data === 'object') {
|
|
263
|
+
for (const [key, value] of Object.entries(args.data)) {
|
|
264
|
+
if (analysis.rootLevelFields.has(key)) {
|
|
265
|
+
errors.push(`Field "${key}" is defined in glossary "fields" array and must be at ROOT LEVEL, not in data object`);
|
|
266
|
+
}
|
|
267
|
+
// Widget validation for data object fields
|
|
268
|
+
if (analysis.widgetFields.has(key)) {
|
|
269
|
+
const widgetValidation = this.validateWidgetValue(key, value, analysis.widgetFields.get(key));
|
|
270
|
+
if (!widgetValidation.valid) {
|
|
271
|
+
errors.push(...widgetValidation.errors);
|
|
272
|
+
}
|
|
273
|
+
if (widgetValidation.warnings.length > 0) {
|
|
274
|
+
warnings.push(...widgetValidation.warnings);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Check for fields at root level that should be in data object
|
|
280
|
+
for (const [key, value] of Object.entries(args)) {
|
|
281
|
+
if (key !== 'type' && key !== 'title' && key !== 'meta' && key !== 'data' &&
|
|
282
|
+
key !== 'reference' && key !== 'referenceType' && key !== 'body' && key !== 'organisation') {
|
|
283
|
+
if (analysis.dataObjectFields.has(key)) {
|
|
284
|
+
errors.push(`Field "${key}" is defined in glossary "definedFields" array and must be in DATA OBJECT, not at root level`);
|
|
285
|
+
}
|
|
286
|
+
// Widget validation for root level fields
|
|
287
|
+
if (analysis.widgetFields.has(key)) {
|
|
288
|
+
const widgetValidation = this.validateWidgetValue(key, value, analysis.widgetFields.get(key));
|
|
289
|
+
if (!widgetValidation.valid) {
|
|
290
|
+
errors.push(...widgetValidation.errors);
|
|
291
|
+
}
|
|
292
|
+
if (widgetValidation.warnings.length > 0) {
|
|
293
|
+
warnings.push(...widgetValidation.warnings);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { valid: errors.length === 0, errors, warnings, analysis };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Automatically restructures payload based on glossary field placement rules
|
|
302
|
+
*/
|
|
303
|
+
restructurePayloadFromGlossary(args, contentType) {
|
|
304
|
+
const analysis = this.analyzeFieldPlacementFromGlossary(contentType);
|
|
305
|
+
const payload = {};
|
|
306
|
+
// Always include title if provided
|
|
307
|
+
if (args.title)
|
|
308
|
+
payload.title = args.title;
|
|
309
|
+
// Handle meta separately (always root level)
|
|
310
|
+
if (args.meta)
|
|
311
|
+
payload.meta = args.meta;
|
|
312
|
+
// Handle standard root-level fields
|
|
313
|
+
const standardRootFields = ['reference', 'referenceType', 'body', 'organisation'];
|
|
314
|
+
for (const fieldKey of standardRootFields) {
|
|
315
|
+
if (args[fieldKey] !== undefined) {
|
|
316
|
+
payload[fieldKey] = args[fieldKey];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Process provided data and separate into correct locations
|
|
320
|
+
const rootData = {};
|
|
321
|
+
const dataObjectData = {};
|
|
322
|
+
if (args.data && typeof args.data === 'object') {
|
|
323
|
+
for (const [key, value] of Object.entries(args.data)) {
|
|
324
|
+
if (analysis.rootLevelFields.has(key)) {
|
|
325
|
+
rootData[key] = value; // Move to root level
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
dataObjectData[key] = value; // Keep in data object
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Add any root-level fields that were incorrectly placed at top level
|
|
333
|
+
for (const [key, value] of Object.entries(args)) {
|
|
334
|
+
if (key !== 'type' && key !== 'title' && key !== 'meta' && key !== 'data' &&
|
|
335
|
+
!standardRootFields.includes(key)) {
|
|
336
|
+
if (analysis.dataObjectFields.has(key)) {
|
|
337
|
+
dataObjectData[key] = value; // Move to data object
|
|
338
|
+
}
|
|
339
|
+
else if (analysis.rootLevelFields.has(key)) {
|
|
340
|
+
rootData[key] = value; // Keep at root level
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Add root level fields to payload
|
|
345
|
+
Object.assign(payload, rootData);
|
|
346
|
+
// Add data object if there are any data fields
|
|
347
|
+
if (Object.keys(dataObjectData).length > 0) {
|
|
348
|
+
payload.data = dataObjectData;
|
|
349
|
+
}
|
|
350
|
+
return payload;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Generates comprehensive error message based on glossary structure with widget guidance
|
|
354
|
+
*/
|
|
355
|
+
generateFieldPlacementErrorFromGlossary(contentType, errors, warnings = []) {
|
|
356
|
+
const analysis = this.analyzeFieldPlacementFromGlossary(contentType);
|
|
357
|
+
const rootFieldsList = Array.from(analysis.rootLevelFields).join(', ');
|
|
358
|
+
const dataFieldsList = Array.from(analysis.dataObjectFields).join(', ');
|
|
359
|
+
let errorMessage = `🚫 **FIELD PLACEMENT ERRORS for ${contentType.toUpperCase()}**
|
|
360
|
+
|
|
361
|
+
${errors.join('\n')}`;
|
|
362
|
+
if (warnings.length > 0) {
|
|
363
|
+
errorMessage += `\n\n⚠️ **WARNINGS:**\n${warnings.join('\n')}`;
|
|
364
|
+
}
|
|
365
|
+
errorMessage += `\n\n**ACCORDING TO GLOSSARY DEFINITION:**
|
|
366
|
+
${analysis.isCustomDefinition ? `- Extends: ${analysis.baseType}` : '- Primitive type'}
|
|
367
|
+
- Total fields in "fields" array: ${analysis.allFields.length}
|
|
368
|
+
- Total fields in "definedFields" array: ${analysis.allDefinedFields.length}`;
|
|
369
|
+
// Add widget information if present
|
|
370
|
+
if (analysis.widgetFields.size > 0) {
|
|
371
|
+
errorMessage += `\n- Widget types detected: ${analysis.widgetFields.size} fields with specific widgets`;
|
|
372
|
+
const widgetInfo = [];
|
|
373
|
+
for (const [fieldKey, widgetType] of analysis.widgetFields.entries()) {
|
|
374
|
+
if (widgetType === 'dateobject') {
|
|
375
|
+
widgetInfo.push(` • ${fieldKey}: dateobject widget (requires {hour, minute, day, month, year} format)`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
widgetInfo.push(` • ${fieldKey}: ${widgetType} widget`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (widgetInfo.length > 0) {
|
|
382
|
+
errorMessage += `\n${widgetInfo.join('\n')}`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
errorMessage += `\n\n**ROOT LEVEL FIELDS (from glossary "fields" array):**
|
|
386
|
+
${rootFieldsList || 'none defined'}
|
|
387
|
+
|
|
388
|
+
**DATA OBJECT FIELDS (from glossary "definedFields" array):**
|
|
389
|
+
${dataFieldsList || 'none defined'}
|
|
390
|
+
|
|
391
|
+
**CORRECT STRUCTURE:**
|
|
392
|
+
\`\`\`json
|
|
393
|
+
{
|
|
394
|
+
"type": "${contentType}",
|
|
395
|
+
"title": "Your Title Here",`;
|
|
396
|
+
// Add sample root level fields with widget-aware examples
|
|
397
|
+
const rootFields = Array.from(analysis.rootLevelFields).filter(f => f !== 'title' && f !== '_id' && f !== 'meta');
|
|
398
|
+
if (rootFields.length > 0) {
|
|
399
|
+
rootFields.slice(0, 2).forEach(fieldKey => {
|
|
400
|
+
const widgetType = analysis.widgetFields.get(fieldKey);
|
|
401
|
+
let exampleValue = '"value"';
|
|
402
|
+
if (widgetType === 'dateobject') {
|
|
403
|
+
exampleValue = '{"hour": 14, "minute": 30, "day": 15, "month": 1, "year": 2025}';
|
|
404
|
+
}
|
|
405
|
+
else if (widgetType === 'date') {
|
|
406
|
+
exampleValue = '"2025-01-15"';
|
|
407
|
+
}
|
|
408
|
+
else if (widgetType === 'time') {
|
|
409
|
+
exampleValue = '"14:30"';
|
|
410
|
+
}
|
|
411
|
+
else if (widgetType === 'datetime') {
|
|
412
|
+
exampleValue = '"2025-01-15T14:30:00Z"';
|
|
413
|
+
}
|
|
414
|
+
errorMessage += `\n "${fieldKey}": ${exampleValue},`;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
errorMessage += `\n "meta": { "scopes": ["scope-id-here"] }`;
|
|
418
|
+
// Add data object with widget-aware examples
|
|
419
|
+
if (analysis.dataObjectFields.size > 0) {
|
|
420
|
+
errorMessage += `,\n "data": {`;
|
|
421
|
+
const dataFields = Array.from(analysis.dataObjectFields).slice(0, 3);
|
|
422
|
+
dataFields.forEach((fieldKey, index) => {
|
|
423
|
+
const widgetType = analysis.widgetFields.get(fieldKey);
|
|
424
|
+
let exampleValue = '"value"';
|
|
425
|
+
if (widgetType === 'dateobject') {
|
|
426
|
+
exampleValue = '{"hour": 9, "minute": 0, "day": 22, "month": 3, "year": 2024}';
|
|
427
|
+
}
|
|
428
|
+
else if (widgetType === 'date') {
|
|
429
|
+
exampleValue = '"2024-03-22"';
|
|
430
|
+
}
|
|
431
|
+
else if (widgetType === 'time') {
|
|
432
|
+
exampleValue = '"09:00"';
|
|
433
|
+
}
|
|
434
|
+
else if (widgetType === 'datetime') {
|
|
435
|
+
exampleValue = '"2024-03-22T09:00:00Z"';
|
|
436
|
+
}
|
|
437
|
+
const comma = index < dataFields.length - 1 ? ',' : '';
|
|
438
|
+
errorMessage += `\n "${fieldKey}": ${exampleValue}${comma}`;
|
|
439
|
+
});
|
|
440
|
+
errorMessage += `\n }`;
|
|
441
|
+
}
|
|
442
|
+
errorMessage += `\n}
|
|
443
|
+
\`\`\`
|
|
444
|
+
|
|
445
|
+
**FIELD PLACEMENT RULE:**
|
|
446
|
+
- Fields in glossary "fields" array → ROOT LEVEL
|
|
447
|
+
- Fields in glossary "definedFields" array → DATA OBJECT`;
|
|
448
|
+
// Add widget-specific guidance
|
|
449
|
+
if (analysis.widgetFields.size > 0) {
|
|
450
|
+
errorMessage += `\n\n**WIDGET-SPECIFIC GUIDANCE:**\n`;
|
|
451
|
+
for (const [fieldKey, widgetType] of analysis.widgetFields.entries()) {
|
|
452
|
+
if (widgetType === 'dateobject') {
|
|
453
|
+
errorMessage += `- **${fieldKey}** (dateobject): Use format {"hour": 14, "minute": 30, "day": 15, "month": 1, "year": 2025}\n`;
|
|
454
|
+
}
|
|
455
|
+
else if (widgetType === 'date') {
|
|
456
|
+
errorMessage += `- **${fieldKey}** (date): Use ISO date format "2025-01-15"\n`;
|
|
457
|
+
}
|
|
458
|
+
else if (widgetType === 'time') {
|
|
459
|
+
errorMessage += `- **${fieldKey}** (time): Use time format "14:30"\n`;
|
|
460
|
+
}
|
|
461
|
+
else if (widgetType === 'datetime') {
|
|
462
|
+
errorMessage += `- **${fieldKey}** (datetime): Use ISO datetime format "2025-01-15T14:30:00Z"\n`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return errorMessage;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Validates widget-specific field values
|
|
470
|
+
*/
|
|
471
|
+
validateWidgetValue(fieldKey, value, widgetType) {
|
|
472
|
+
const errors = [];
|
|
473
|
+
const warnings = [];
|
|
474
|
+
switch (widgetType) {
|
|
475
|
+
case 'dateobject':
|
|
476
|
+
return this.validateDateObjectWidget(fieldKey, value);
|
|
477
|
+
case 'date':
|
|
478
|
+
if (typeof value === 'string') {
|
|
479
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
480
|
+
if (!dateRegex.test(value)) {
|
|
481
|
+
errors.push(`Field "${fieldKey}" with date widget must use ISO date format (YYYY-MM-DD), got: ${value}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
errors.push(`Field "${fieldKey}" with date widget must be a string in ISO date format (YYYY-MM-DD)`);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case 'time':
|
|
489
|
+
if (typeof value === 'string') {
|
|
490
|
+
const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;
|
|
491
|
+
if (!timeRegex.test(value)) {
|
|
492
|
+
errors.push(`Field "${fieldKey}" with time widget must use time format (HH:MM or HH:MM:SS), got: ${value}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
errors.push(`Field "${fieldKey}" with time widget must be a string in time format (HH:MM)`);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
case 'datetime':
|
|
500
|
+
if (typeof value === 'string') {
|
|
501
|
+
try {
|
|
502
|
+
new Date(value);
|
|
503
|
+
}
|
|
504
|
+
catch (e) {
|
|
505
|
+
errors.push(`Field "${fieldKey}" with datetime widget must be a valid ISO datetime string, got: ${value}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
errors.push(`Field "${fieldKey}" with datetime widget must be a string in ISO datetime format`);
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
default:
|
|
513
|
+
// No specific validation for other widget types
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Validates dateobject widget values
|
|
520
|
+
*/
|
|
521
|
+
validateDateObjectWidget(fieldKey, value) {
|
|
522
|
+
const errors = [];
|
|
523
|
+
const warnings = [];
|
|
524
|
+
if (!value || typeof value !== 'object') {
|
|
525
|
+
errors.push(`Field "${fieldKey}" with dateobject widget must be an object with hour, minute, day, month, year properties`);
|
|
526
|
+
return { valid: false, errors, warnings };
|
|
527
|
+
}
|
|
528
|
+
const requiredProps = ['hour', 'minute', 'day', 'month', 'year'];
|
|
529
|
+
const missingProps = requiredProps.filter(prop => value[prop] === undefined || value[prop] === null);
|
|
530
|
+
if (missingProps.length > 0) {
|
|
531
|
+
errors.push(`Field "${fieldKey}" dateobject is missing required properties: ${missingProps.join(', ')}`);
|
|
532
|
+
}
|
|
533
|
+
// Validate ranges
|
|
534
|
+
if (typeof value.hour === 'number') {
|
|
535
|
+
if (value.hour < 0 || value.hour > 23) {
|
|
536
|
+
errors.push(`Field "${fieldKey}" dateobject hour must be between 0-23, got: ${value.hour}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else if (value.hour !== undefined) {
|
|
540
|
+
errors.push(`Field "${fieldKey}" dateobject hour must be a number`);
|
|
541
|
+
}
|
|
542
|
+
if (typeof value.minute === 'number') {
|
|
543
|
+
if (value.minute < 0 || value.minute > 59) {
|
|
544
|
+
errors.push(`Field "${fieldKey}" dateobject minute must be between 0-59, got: ${value.minute}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else if (value.minute !== undefined) {
|
|
548
|
+
errors.push(`Field "${fieldKey}" dateobject minute must be a number`);
|
|
549
|
+
}
|
|
550
|
+
if (typeof value.day === 'number') {
|
|
551
|
+
if (value.day < 1 || value.day > 31) {
|
|
552
|
+
errors.push(`Field "${fieldKey}" dateobject day must be between 1-31, got: ${value.day}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else if (value.day !== undefined) {
|
|
556
|
+
errors.push(`Field "${fieldKey}" dateobject day must be a number`);
|
|
557
|
+
}
|
|
558
|
+
if (typeof value.month === 'number') {
|
|
559
|
+
if (value.month < 1 || value.month > 12) {
|
|
560
|
+
errors.push(`Field "${fieldKey}" dateobject month must be between 1-12, got: ${value.month}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
else if (value.month !== undefined) {
|
|
564
|
+
errors.push(`Field "${fieldKey}" dateobject month must be a number`);
|
|
565
|
+
}
|
|
566
|
+
if (typeof value.year === 'number') {
|
|
567
|
+
if (value.year < 1900 || value.year > 2100) {
|
|
568
|
+
warnings.push(`Field "${fieldKey}" dateobject year ${value.year} seems unusual (expected 1900-2100)`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
else if (value.year !== undefined) {
|
|
572
|
+
errors.push(`Field "${fieldKey}" dateobject year must be a number`);
|
|
573
|
+
}
|
|
574
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
575
|
+
}
|
|
212
576
|
generateFieldPath(field, isDefinedField, groupPath = '') {
|
|
213
577
|
let basePath = isDefinedField ? 'data' : '';
|
|
214
578
|
if (groupPath) {
|
|
@@ -371,22 +735,47 @@ export class QikMCPServer {
|
|
|
371
735
|
for (const fieldKey of commonRootFields) {
|
|
372
736
|
properties[fieldKey] = {
|
|
373
737
|
type: 'string',
|
|
374
|
-
description: `${fieldKey} field (goes at
|
|
738
|
+
description: `${fieldKey} field (ALWAYS goes at ROOT LEVEL, never in data object)`,
|
|
375
739
|
};
|
|
376
740
|
}
|
|
377
|
-
// Add a data object for definedFields
|
|
741
|
+
// Add a data object for definedFields with clear explanation
|
|
378
742
|
properties.data = {
|
|
379
743
|
type: 'object',
|
|
380
|
-
description:
|
|
744
|
+
description: `CRITICAL FIELD PLACEMENT RULE:
|
|
745
|
+
|
|
746
|
+
**ROOT LEVEL FIELDS (never put these in data object):**
|
|
747
|
+
- title, reference, referenceType, body, organisation, meta
|
|
748
|
+
- These are "fields" in the glossary definition
|
|
749
|
+
|
|
750
|
+
**DATA OBJECT FIELDS (always put these in data object):**
|
|
751
|
+
- Custom fields defined as "definedFields" in the glossary
|
|
752
|
+
- Content-type specific fields like wasAnyoneHurt, customField1, etc.
|
|
753
|
+
|
|
754
|
+
**WRONG EXAMPLE:**
|
|
755
|
+
{
|
|
756
|
+
"data": {
|
|
757
|
+
"title": "My Title", // ❌ WRONG - title goes at root level
|
|
758
|
+
"plural": "My Items", // ❌ WRONG - plural goes at root level
|
|
759
|
+
"customField": "value"
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
**CORRECT EXAMPLE:**
|
|
764
|
+
{
|
|
765
|
+
"title": "My Title", // ✅ CORRECT - at root level
|
|
766
|
+
"data": {
|
|
767
|
+
"customField": "value" // ✅ CORRECT - custom fields in data object
|
|
768
|
+
}
|
|
769
|
+
}`,
|
|
381
770
|
properties: {
|
|
382
771
|
wasAnyoneHurt: {
|
|
383
772
|
type: 'string',
|
|
384
|
-
description: 'For incident reports - whether anyone was hurt',
|
|
773
|
+
description: 'For incident reports - whether anyone was hurt (this is a definedField, so it goes in data object)',
|
|
385
774
|
enum: ['yes', 'no']
|
|
386
775
|
},
|
|
387
776
|
whatTimeDidItHappen: {
|
|
388
777
|
type: 'string',
|
|
389
|
-
description: 'For incident reports - when the incident occurred'
|
|
778
|
+
description: 'For incident reports - when the incident occurred (this is a definedField, so it goes in data object)'
|
|
390
779
|
}
|
|
391
780
|
}
|
|
392
781
|
};
|
|
@@ -1613,6 +2002,46 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
|
|
|
1613
2002
|
required: ['firstName', 'lastName'],
|
|
1614
2003
|
},
|
|
1615
2004
|
},
|
|
2005
|
+
{
|
|
2006
|
+
name: 'qik_get_profile_timeline',
|
|
2007
|
+
description: this.generateToolDescription('Get a profile timeline with chronological activity data - provides much richer information than basic profile details including recent actions, content created, events attended, workflow activities, and other activity logs', '📅'),
|
|
2008
|
+
inputSchema: {
|
|
2009
|
+
type: 'object',
|
|
2010
|
+
properties: {
|
|
2011
|
+
id: {
|
|
2012
|
+
type: 'string',
|
|
2013
|
+
description: 'Profile ID to get timeline for',
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
required: ['id'],
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
name: 'qik_get_profile_info',
|
|
2021
|
+
description: this.generateToolDescription('Intelligently get profile information with disambiguation between basic details and activity timeline. Asks clarifying questions when the request is ambiguous to provide the most relevant information', '🤔'),
|
|
2022
|
+
inputSchema: {
|
|
2023
|
+
type: 'object',
|
|
2024
|
+
properties: {
|
|
2025
|
+
query: {
|
|
2026
|
+
type: 'string',
|
|
2027
|
+
description: 'Natural language query about a person (e.g., "Tell me about Jeff", "What has John been up to?", "Get Sarah\'s contact info")',
|
|
2028
|
+
},
|
|
2029
|
+
profileId: {
|
|
2030
|
+
type: 'string',
|
|
2031
|
+
description: 'Profile ID if known (optional - can search by name if not provided)',
|
|
2032
|
+
},
|
|
2033
|
+
profileName: {
|
|
2034
|
+
type: 'string',
|
|
2035
|
+
description: 'Person\'s name to search for if ID not provided (optional)',
|
|
2036
|
+
},
|
|
2037
|
+
includeTimeline: {
|
|
2038
|
+
type: 'boolean',
|
|
2039
|
+
description: 'Whether to include timeline/activity data (optional - will be determined from query if not specified)',
|
|
2040
|
+
},
|
|
2041
|
+
},
|
|
2042
|
+
required: ['query'],
|
|
2043
|
+
},
|
|
2044
|
+
},
|
|
1616
2045
|
// Form Management
|
|
1617
2046
|
{
|
|
1618
2047
|
name: 'qik_get_form',
|
|
@@ -1838,6 +2267,10 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
|
|
|
1838
2267
|
return await this.getScopes();
|
|
1839
2268
|
case 'qik_get_smartlist':
|
|
1840
2269
|
return await this.getSmartlist(request.params.arguments);
|
|
2270
|
+
case 'qik_get_profile_timeline':
|
|
2271
|
+
return await this.getProfileTimeline(request.params.arguments);
|
|
2272
|
+
case 'qik_get_profile_info':
|
|
2273
|
+
return await this.getProfileInfo(request.params.arguments);
|
|
1841
2274
|
case 'qik_create_content_intelligent':
|
|
1842
2275
|
return await this.intelligentContentCreation(request.params.arguments.description, {
|
|
1843
2276
|
title: request.params.arguments.title,
|
|
@@ -2099,90 +2532,58 @@ ${JSON.stringify({
|
|
|
2099
2532
|
isError: true,
|
|
2100
2533
|
};
|
|
2101
2534
|
}
|
|
2102
|
-
//
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
fields = contentTypeInfo.definition.fields || [];
|
|
2115
|
-
definedFields = contentTypeInfo.definition.definedFields || [];
|
|
2116
|
-
baseType = contentTypeInfo.definition.definesType;
|
|
2117
|
-
}
|
|
2118
|
-
else if (contentTypeInfo.type) {
|
|
2119
|
-
fields = contentTypeInfo.type.fields || [];
|
|
2120
|
-
definedFields = contentTypeInfo.type.definedFields || [];
|
|
2121
|
-
baseType = contentTypeInfo.type.definesType;
|
|
2122
|
-
}
|
|
2123
|
-
// Structure payload correctly based on field definitions
|
|
2124
|
-
const payload = {
|
|
2125
|
-
title: args.title,
|
|
2126
|
-
meta: args.meta || {},
|
|
2127
|
-
};
|
|
2128
|
-
// Separate fields into root level vs data object
|
|
2129
|
-
const rootLevelData = {};
|
|
2130
|
-
const dataObjectData = {};
|
|
2131
|
-
// Handle direct field assignments from args (for fields like reference, referenceType, body)
|
|
2132
|
-
const directFields = ['reference', 'referenceType', 'body', 'organisation'];
|
|
2133
|
-
for (const fieldKey of directFields) {
|
|
2134
|
-
if (args[fieldKey] !== undefined) {
|
|
2135
|
-
rootLevelData[fieldKey] = args[fieldKey];
|
|
2535
|
+
// Use the new glossary-driven field placement validation
|
|
2536
|
+
try {
|
|
2537
|
+
const fieldValidation = this.validateFieldPlacementFromGlossary(args, args.type);
|
|
2538
|
+
// If there are field placement errors, return detailed guidance
|
|
2539
|
+
if (!fieldValidation.valid) {
|
|
2540
|
+
return {
|
|
2541
|
+
content: [{
|
|
2542
|
+
type: 'text',
|
|
2543
|
+
text: this.generateFieldPlacementErrorFromGlossary(args.type, fieldValidation.errors),
|
|
2544
|
+
}],
|
|
2545
|
+
isError: true,
|
|
2546
|
+
};
|
|
2136
2547
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
//
|
|
2140
|
-
const
|
|
2141
|
-
const
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2548
|
+
// Automatically restructure the payload based on glossary rules
|
|
2549
|
+
const payload = this.restructurePayloadFromGlossary(args, args.type);
|
|
2550
|
+
// Handle scope inheritance for comment types
|
|
2551
|
+
const analysis = fieldValidation.analysis;
|
|
2552
|
+
const isCommentType = analysis.baseType === 'comment' || args.type === 'comment' || args.type.includes('Comment');
|
|
2553
|
+
if (isCommentType && payload.reference && (!payload.meta?.scopes || payload.meta.scopes.length === 0)) {
|
|
2554
|
+
try {
|
|
2555
|
+
// Fetch the referenced item to get its scopes
|
|
2556
|
+
const referencedItem = await this.axiosInstance.get(`/content/${payload.reference}`);
|
|
2557
|
+
if (referencedItem.data && referencedItem.data.meta && referencedItem.data.meta.scopes) {
|
|
2558
|
+
if (!payload.meta)
|
|
2559
|
+
payload.meta = {};
|
|
2560
|
+
payload.meta.scopes = referencedItem.data.meta.scopes;
|
|
2561
|
+
this.log(`Inherited scopes from referenced item ${payload.reference}: ${payload.meta.scopes.join(', ')}`);
|
|
2562
|
+
}
|
|
2147
2563
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
}
|
|
2152
|
-
else {
|
|
2153
|
-
// Unknown field - put in data object as fallback
|
|
2154
|
-
dataObjectData[key] = value;
|
|
2564
|
+
catch (error) {
|
|
2565
|
+
this.log(`Failed to fetch referenced item ${payload.reference} for scope inheritance: ${this.formatError(error)}`);
|
|
2566
|
+
// Continue without scope inheritance if we can't fetch the referenced item
|
|
2155
2567
|
}
|
|
2156
2568
|
}
|
|
2157
|
-
}
|
|
2158
|
-
// Add root level fields to payload
|
|
2159
|
-
Object.assign(payload, rootLevelData);
|
|
2160
|
-
// Add data object fields if any exist
|
|
2161
|
-
if (Object.keys(dataObjectData).length > 0) {
|
|
2162
|
-
payload.data = dataObjectData;
|
|
2163
|
-
}
|
|
2164
|
-
// Handle scope inheritance for comment types
|
|
2165
|
-
const isCommentType = baseType === 'comment' || args.type === 'comment' || args.type.includes('Comment');
|
|
2166
|
-
if (isCommentType && payload.reference && (!payload.meta.scopes || payload.meta.scopes.length === 0)) {
|
|
2167
|
-
try {
|
|
2168
|
-
// Fetch the referenced item to get its scopes
|
|
2169
|
-
const referencedItem = await this.axiosInstance.get(`/content/${payload.reference}`);
|
|
2170
|
-
if (referencedItem.data && referencedItem.data.meta && referencedItem.data.meta.scopes) {
|
|
2171
|
-
payload.meta.scopes = referencedItem.data.meta.scopes;
|
|
2172
|
-
this.log(`Inherited scopes from referenced item ${payload.reference}: ${payload.meta.scopes.join(', ')}`);
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
catch (error) {
|
|
2176
|
-
this.log(`Failed to fetch referenced item ${payload.reference} for scope inheritance: ${this.formatError(error)}`);
|
|
2177
|
-
// Continue without scope inheritance if we can't fetch the referenced item
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
try {
|
|
2181
2569
|
const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
|
|
2182
2570
|
return {
|
|
2183
2571
|
content: [{
|
|
2184
2572
|
type: 'text',
|
|
2185
|
-
text:
|
|
2573
|
+
text: `✅ **SUCCESSFULLY CREATED ${args.type.toUpperCase()} CONTENT**
|
|
2574
|
+
|
|
2575
|
+
The content was created using glossary-driven field placement:
|
|
2576
|
+
- Root level fields: ${Object.keys(payload).filter(k => k !== 'data' && k !== 'meta').join(', ')}
|
|
2577
|
+
- Data object fields: ${payload.data ? Object.keys(payload.data).join(', ') : 'none'}
|
|
2578
|
+
- Meta fields: ${payload.meta ? Object.keys(payload.meta).join(', ') : 'none'}
|
|
2579
|
+
|
|
2580
|
+
**Field Placement Analysis:**
|
|
2581
|
+
- Content type: ${analysis.isCustomDefinition ? `Custom definition extending ${analysis.baseType}` : 'Primitive type'}
|
|
2582
|
+
- Root level fields from glossary: ${Array.from(analysis.rootLevelFields).join(', ')}
|
|
2583
|
+
- Data object fields from glossary: ${Array.from(analysis.dataObjectFields).join(', ')}
|
|
2584
|
+
|
|
2585
|
+
**Created Content:**
|
|
2586
|
+
${JSON.stringify(response.data, null, 2)}`,
|
|
2186
2587
|
}],
|
|
2187
2588
|
};
|
|
2188
2589
|
}
|
|
@@ -2289,6 +2690,180 @@ ${JSON.stringify({
|
|
|
2289
2690
|
};
|
|
2290
2691
|
}
|
|
2291
2692
|
}
|
|
2693
|
+
async getProfileTimeline(args) {
|
|
2694
|
+
try {
|
|
2695
|
+
const response = await this.axiosInstance.get(`/profile/${args.id}/timeline`);
|
|
2696
|
+
return {
|
|
2697
|
+
content: [{
|
|
2698
|
+
type: 'text',
|
|
2699
|
+
text: `📅 **PROFILE TIMELINE FOR ${args.id}**
|
|
2700
|
+
|
|
2701
|
+
This timeline provides chronological activity data including recent actions, content created, events attended, workflow activities, and other activity logs.
|
|
2702
|
+
|
|
2703
|
+
**Timeline Data:**
|
|
2704
|
+
${JSON.stringify(response.data, null, 2)}`,
|
|
2705
|
+
}],
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
catch (error) {
|
|
2709
|
+
return {
|
|
2710
|
+
content: [{
|
|
2711
|
+
type: 'text',
|
|
2712
|
+
text: `Failed to get profile timeline for ${args.id}: ${this.formatError(error)}`,
|
|
2713
|
+
}],
|
|
2714
|
+
isError: true,
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
async getProfileInfo(args) {
|
|
2719
|
+
const normalizedQuery = args.query.toLowerCase().trim();
|
|
2720
|
+
// Analyze the query to determine intent
|
|
2721
|
+
const timelineKeywords = [
|
|
2722
|
+
'activity', 'timeline', 'been up to', 'doing', 'recent', 'lately',
|
|
2723
|
+
'actions', 'history', 'events', 'workflow', 'progress'
|
|
2724
|
+
];
|
|
2725
|
+
const basicInfoKeywords = [
|
|
2726
|
+
'contact', 'email', 'phone', 'details', 'information', 'about'
|
|
2727
|
+
];
|
|
2728
|
+
const wantsTimeline = args.includeTimeline !== undefined
|
|
2729
|
+
? args.includeTimeline
|
|
2730
|
+
: timelineKeywords.some(keyword => normalizedQuery.includes(keyword));
|
|
2731
|
+
const wantsBasicInfo = basicInfoKeywords.some(keyword => normalizedQuery.includes(keyword));
|
|
2732
|
+
// If the intent is ambiguous, ask for clarification
|
|
2733
|
+
if (!wantsTimeline && !wantsBasicInfo && args.includeTimeline === undefined) {
|
|
2734
|
+
const personName = args.profileName || (args.profileId ? `person (ID: ${args.profileId})` : 'this person');
|
|
2735
|
+
return {
|
|
2736
|
+
content: [{
|
|
2737
|
+
type: 'text',
|
|
2738
|
+
text: `🤔 **CLARIFICATION NEEDED**
|
|
2739
|
+
|
|
2740
|
+
You asked: "${args.query}"
|
|
2741
|
+
|
|
2742
|
+
I can provide different types of information about ${personName}:
|
|
2743
|
+
|
|
2744
|
+
**OPTION 1: Basic Profile Details** 📋
|
|
2745
|
+
- Contact information (email, phone)
|
|
2746
|
+
- Basic demographic data
|
|
2747
|
+
- Profile fields and custom data
|
|
2748
|
+
→ Use: \`qik_get_content\` or \`qik_list_profiles\`
|
|
2749
|
+
|
|
2750
|
+
**OPTION 2: Activity Timeline** 📅
|
|
2751
|
+
- Recent actions and activities
|
|
2752
|
+
- Content they've created or been involved with
|
|
2753
|
+
- Workflow progress and events attended
|
|
2754
|
+
- Much richer, more colorful activity data
|
|
2755
|
+
→ Use: \`qik_get_profile_timeline\`
|
|
2756
|
+
|
|
2757
|
+
**Which would be more helpful for your needs?**
|
|
2758
|
+
|
|
2759
|
+
To get both, you can:
|
|
2760
|
+
1. First get basic profile info with \`qik_list_profiles\` (search by name)
|
|
2761
|
+
2. Then get timeline data with \`qik_get_profile_timeline\` (using the profile ID)`,
|
|
2762
|
+
}],
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
let profileId = args.profileId;
|
|
2766
|
+
// If no profile ID provided, try to find the profile by name
|
|
2767
|
+
if (!profileId && args.profileName) {
|
|
2768
|
+
try {
|
|
2769
|
+
const searchResponse = await this.axiosInstance.post('/content/profile/list', {
|
|
2770
|
+
search: args.profileName,
|
|
2771
|
+
page: { size: 5, index: 1 },
|
|
2772
|
+
});
|
|
2773
|
+
if (searchResponse.data.items && searchResponse.data.items.length > 0) {
|
|
2774
|
+
if (searchResponse.data.items.length === 1) {
|
|
2775
|
+
profileId = searchResponse.data.items[0]._id;
|
|
2776
|
+
}
|
|
2777
|
+
else {
|
|
2778
|
+
// Multiple matches found
|
|
2779
|
+
const matches = searchResponse.data.items.map((p) => `- **${p._id}**: ${p.firstName} ${p.lastName} (${p.emails?.[0] || 'no email'})`).join('\n');
|
|
2780
|
+
return {
|
|
2781
|
+
content: [{
|
|
2782
|
+
type: 'text',
|
|
2783
|
+
text: `🔍 **MULTIPLE PROFILES FOUND**
|
|
2784
|
+
|
|
2785
|
+
Found ${searchResponse.data.items.length} profiles matching "${args.profileName}":
|
|
2786
|
+
|
|
2787
|
+
${matches}
|
|
2788
|
+
|
|
2789
|
+
Please specify which profile you want by using the profile ID with \`qik_get_profile_timeline\` or \`qik_get_content\`.`,
|
|
2790
|
+
}],
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
else {
|
|
2795
|
+
return {
|
|
2796
|
+
content: [{
|
|
2797
|
+
type: 'text',
|
|
2798
|
+
text: `❌ **PROFILE NOT FOUND**
|
|
2799
|
+
|
|
2800
|
+
No profiles found matching "${args.profileName}".
|
|
2801
|
+
|
|
2802
|
+
Try:
|
|
2803
|
+
- Using \`qik_list_profiles\` with a broader search
|
|
2804
|
+
- Checking the spelling of the name
|
|
2805
|
+
- Using the exact profile ID if you have it`,
|
|
2806
|
+
}],
|
|
2807
|
+
isError: true,
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
catch (error) {
|
|
2812
|
+
return {
|
|
2813
|
+
content: [{
|
|
2814
|
+
type: 'text',
|
|
2815
|
+
text: `Failed to search for profile "${args.profileName}": ${this.formatError(error)}`,
|
|
2816
|
+
}],
|
|
2817
|
+
isError: true,
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
// Now we have a profile ID, get the appropriate information
|
|
2822
|
+
if (wantsTimeline && profileId) {
|
|
2823
|
+
return await this.getProfileTimeline({ id: profileId });
|
|
2824
|
+
}
|
|
2825
|
+
else if (profileId) {
|
|
2826
|
+
// Get basic profile information
|
|
2827
|
+
try {
|
|
2828
|
+
const response = await this.axiosInstance.get(`/content/${profileId}`);
|
|
2829
|
+
return {
|
|
2830
|
+
content: [{
|
|
2831
|
+
type: 'text',
|
|
2832
|
+
text: `👤 **PROFILE INFORMATION**
|
|
2833
|
+
|
|
2834
|
+
**Basic Details:**
|
|
2835
|
+
${JSON.stringify(response.data, null, 2)}
|
|
2836
|
+
|
|
2837
|
+
💡 **Want more activity details?** Use \`qik_get_profile_timeline\` with ID: ${profileId} to see their recent activities, workflow progress, and timeline data.`,
|
|
2838
|
+
}],
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
catch (error) {
|
|
2842
|
+
return {
|
|
2843
|
+
content: [{
|
|
2844
|
+
type: 'text',
|
|
2845
|
+
text: `Failed to get profile information for ${profileId}: ${this.formatError(error)}`,
|
|
2846
|
+
}],
|
|
2847
|
+
isError: true,
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
else {
|
|
2852
|
+
return {
|
|
2853
|
+
content: [{
|
|
2854
|
+
type: 'text',
|
|
2855
|
+
text: `❌ **MISSING PROFILE IDENTIFIER**
|
|
2856
|
+
|
|
2857
|
+
To get profile information, I need either:
|
|
2858
|
+
- **profileId**: The exact profile ID
|
|
2859
|
+
- **profileName**: The person's name to search for
|
|
2860
|
+
|
|
2861
|
+
Please provide one of these and try again.`,
|
|
2862
|
+
}],
|
|
2863
|
+
isError: true,
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2292
2867
|
async getForm(args) {
|
|
2293
2868
|
try {
|
|
2294
2869
|
const response = await this.axiosInstance.get(`/form/${args.id}`);
|
|
@@ -2433,6 +3008,395 @@ ${JSON.stringify({
|
|
|
2433
3008
|
};
|
|
2434
3009
|
}
|
|
2435
3010
|
}
|
|
3011
|
+
// Documentation-focused tool implementations
|
|
3012
|
+
async getDocumentation(args) {
|
|
3013
|
+
try {
|
|
3014
|
+
const documentation = QikDocumentationHelper.getTopicDocumentation(args.topic);
|
|
3015
|
+
if (!documentation) {
|
|
3016
|
+
return {
|
|
3017
|
+
content: [{
|
|
3018
|
+
type: 'text',
|
|
3019
|
+
text: `❌ **DOCUMENTATION NOT FOUND**
|
|
3020
|
+
|
|
3021
|
+
No documentation found for topic: "${args.topic}"
|
|
3022
|
+
|
|
3023
|
+
**Available topics:**
|
|
3024
|
+
- authentication (token types, methods, error codes)
|
|
3025
|
+
- endpoints (API endpoint documentation)
|
|
3026
|
+
- contentTypes (content type specific guidance)
|
|
3027
|
+
- filterSyntax (filter operators and comparators)
|
|
3028
|
+
- concepts (key concepts like field placement, scopes, workflows)
|
|
3029
|
+
- examples (code examples for common use cases)
|
|
3030
|
+
- troubleshooting (error resolution guides)
|
|
3031
|
+
|
|
3032
|
+
**Example usage:**
|
|
3033
|
+
- \`qik_get_documentation\` with topic: "concepts.field_placement"
|
|
3034
|
+
- \`qik_get_documentation\` with topic: "authentication.tokenTypes"
|
|
3035
|
+
- \`qik_get_documentation\` with topic: "troubleshooting.field_placement_errors"`,
|
|
3036
|
+
}],
|
|
3037
|
+
isError: true,
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
let result = `📖 **DOCUMENTATION: ${args.topic.toUpperCase()}**\n\n`;
|
|
3041
|
+
if (args.subtopic && documentation[args.subtopic]) {
|
|
3042
|
+
result += `**Subtopic: ${args.subtopic}**\n\n`;
|
|
3043
|
+
result += JSON.stringify(documentation[args.subtopic], null, 2);
|
|
3044
|
+
}
|
|
3045
|
+
else {
|
|
3046
|
+
result += JSON.stringify(documentation, null, 2);
|
|
3047
|
+
}
|
|
3048
|
+
return {
|
|
3049
|
+
content: [{
|
|
3050
|
+
type: 'text',
|
|
3051
|
+
text: result,
|
|
3052
|
+
}],
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
catch (error) {
|
|
3056
|
+
return {
|
|
3057
|
+
content: [{
|
|
3058
|
+
type: 'text',
|
|
3059
|
+
text: `Failed to get documentation for ${args.topic}: ${this.formatError(error)}`,
|
|
3060
|
+
}],
|
|
3061
|
+
isError: true,
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
async searchDocumentation(args) {
|
|
3066
|
+
try {
|
|
3067
|
+
const results = QikDocumentationHelper.searchDocumentation(args.query);
|
|
3068
|
+
const limit = args.limit || 10;
|
|
3069
|
+
const limitedResults = results.slice(0, limit);
|
|
3070
|
+
if (limitedResults.length === 0) {
|
|
3071
|
+
return {
|
|
3072
|
+
content: [{
|
|
3073
|
+
type: 'text',
|
|
3074
|
+
text: `🔍 **NO DOCUMENTATION FOUND**
|
|
3075
|
+
|
|
3076
|
+
No documentation found for query: "${args.query}"
|
|
3077
|
+
|
|
3078
|
+
**Try searching for:**
|
|
3079
|
+
- "field placement" - for field structure guidance
|
|
3080
|
+
- "authentication" - for token and auth help
|
|
3081
|
+
- "filter" - for filtering and query syntax
|
|
3082
|
+
- "workflow" - for workflow system concepts
|
|
3083
|
+
- "birthday" - for anniversary/birthday queries
|
|
3084
|
+
- "scope" - for permission and scope guidance
|
|
3085
|
+
- "troubleshooting" - for error resolution
|
|
3086
|
+
|
|
3087
|
+
**Available documentation sections:**
|
|
3088
|
+
- Authentication & Tokens
|
|
3089
|
+
- API Endpoints
|
|
3090
|
+
- Content Types
|
|
3091
|
+
- Filter Syntax
|
|
3092
|
+
- Core Concepts
|
|
3093
|
+
- Code Examples
|
|
3094
|
+
- Troubleshooting Guides`,
|
|
3095
|
+
}],
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
let result = `🔍 **DOCUMENTATION SEARCH RESULTS**\n\n`;
|
|
3099
|
+
result += `Found ${limitedResults.length} results for "${args.query}":\n\n`;
|
|
3100
|
+
limitedResults.forEach((item, index) => {
|
|
3101
|
+
result += `**${index + 1}. ${item.path}** (${item.type})\n`;
|
|
3102
|
+
if (typeof item.content === 'string') {
|
|
3103
|
+
const preview = item.content.length > 200
|
|
3104
|
+
? item.content.substring(0, 200) + '...'
|
|
3105
|
+
: item.content;
|
|
3106
|
+
result += `${preview}\n\n`;
|
|
3107
|
+
}
|
|
3108
|
+
else {
|
|
3109
|
+
result += `${JSON.stringify(item.content).substring(0, 200)}...\n\n`;
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
result += `💡 **Need more specific help?** Use \`qik_get_documentation\` with a specific topic for detailed information.`;
|
|
3113
|
+
return {
|
|
3114
|
+
content: [{
|
|
3115
|
+
type: 'text',
|
|
3116
|
+
text: result,
|
|
3117
|
+
}],
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
catch (error) {
|
|
3121
|
+
return {
|
|
3122
|
+
content: [{
|
|
3123
|
+
type: 'text',
|
|
3124
|
+
text: `Failed to search documentation: ${this.formatError(error)}`,
|
|
3125
|
+
}],
|
|
3126
|
+
isError: true,
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
async getTroubleshooting(args) {
|
|
3131
|
+
try {
|
|
3132
|
+
let troubleshootingInfo;
|
|
3133
|
+
if (args.issueType) {
|
|
3134
|
+
// Get specific issue type
|
|
3135
|
+
troubleshootingInfo = QIK_DOCUMENTATION.troubleshooting[args.issueType];
|
|
3136
|
+
if (troubleshootingInfo) {
|
|
3137
|
+
troubleshootingInfo = { issue: args.issueType, ...troubleshootingInfo };
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
else {
|
|
3141
|
+
// Auto-detect issue type from error message
|
|
3142
|
+
troubleshootingInfo = QikDocumentationHelper.getTroubleshootingInfo(args.errorMessage);
|
|
3143
|
+
}
|
|
3144
|
+
if (!troubleshootingInfo) {
|
|
3145
|
+
return {
|
|
3146
|
+
content: [{
|
|
3147
|
+
type: 'text',
|
|
3148
|
+
text: `🔧 **NO SPECIFIC TROUBLESHOOTING FOUND**
|
|
3149
|
+
|
|
3150
|
+
I couldn't find specific troubleshooting information for: "${args.errorMessage}"
|
|
3151
|
+
|
|
3152
|
+
**Available troubleshooting categories:**
|
|
3153
|
+
- **field_placement_errors**: Field structure and placement issues
|
|
3154
|
+
- **authentication_failures**: Token and permission problems
|
|
3155
|
+
- **filter_syntax_errors**: Query and filter syntax issues
|
|
3156
|
+
- **scope_permission_issues**: Access and permission problems
|
|
3157
|
+
- **workflow_confusion**: Workflow definition vs card confusion
|
|
3158
|
+
- **date_handling_issues**: Date format and timezone problems
|
|
3159
|
+
- **performance_issues**: Slow queries and optimization
|
|
3160
|
+
|
|
3161
|
+
**General troubleshooting steps:**
|
|
3162
|
+
1. Check the error message for specific field names or codes
|
|
3163
|
+
2. Verify your access token and permissions
|
|
3164
|
+
3. Ensure proper field placement (root vs data object)
|
|
3165
|
+
4. Validate filter syntax and comparators
|
|
3166
|
+
5. Check scope permissions and hierarchy
|
|
3167
|
+
|
|
3168
|
+
**Need more help?** Try:
|
|
3169
|
+
- \`qik_search_documentation\` with keywords from your error
|
|
3170
|
+
- \`qik_get_documentation\` with topic: "troubleshooting"
|
|
3171
|
+
- \`qik_validate_field_placement\` for content creation issues`,
|
|
3172
|
+
}],
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
let result = `🔧 **TROUBLESHOOTING: ${troubleshootingInfo.issue.toUpperCase().replace(/_/g, ' ')}**\n\n`;
|
|
3176
|
+
result += `**Your Error:** "${args.errorMessage}"\n\n`;
|
|
3177
|
+
if (troubleshootingInfo.symptoms && troubleshootingInfo.symptoms.length > 0) {
|
|
3178
|
+
result += `**Common Symptoms:**\n`;
|
|
3179
|
+
result += troubleshootingInfo.symptoms.map((s) => `- ${s}`).join('\n');
|
|
3180
|
+
result += '\n\n';
|
|
3181
|
+
}
|
|
3182
|
+
if (troubleshootingInfo.causes && troubleshootingInfo.causes.length > 0) {
|
|
3183
|
+
result += `**Likely Causes:**\n`;
|
|
3184
|
+
result += troubleshootingInfo.causes.map((c) => `- ${c}`).join('\n');
|
|
3185
|
+
result += '\n\n';
|
|
3186
|
+
}
|
|
3187
|
+
if (troubleshootingInfo.solutions && troubleshootingInfo.solutions.length > 0) {
|
|
3188
|
+
result += `**Solutions:**\n`;
|
|
3189
|
+
result += troubleshootingInfo.solutions.map((s) => `- ${s}`).join('\n');
|
|
3190
|
+
result += '\n\n';
|
|
3191
|
+
}
|
|
3192
|
+
if (troubleshootingInfo.prevention && troubleshootingInfo.prevention.length > 0) {
|
|
3193
|
+
result += `**Prevention:**\n`;
|
|
3194
|
+
result += troubleshootingInfo.prevention.map((p) => `- ${p}`).join('\n');
|
|
3195
|
+
result += '\n\n';
|
|
3196
|
+
}
|
|
3197
|
+
if (troubleshootingInfo.relatedIssues && troubleshootingInfo.relatedIssues.length > 0) {
|
|
3198
|
+
result += `**Related Issues:**\n`;
|
|
3199
|
+
result += troubleshootingInfo.relatedIssues.map((r) => `- ${r}`).join('\n');
|
|
3200
|
+
}
|
|
3201
|
+
return {
|
|
3202
|
+
content: [{
|
|
3203
|
+
type: 'text',
|
|
3204
|
+
text: result,
|
|
3205
|
+
}],
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
catch (error) {
|
|
3209
|
+
return {
|
|
3210
|
+
content: [{
|
|
3211
|
+
type: 'text',
|
|
3212
|
+
text: `Failed to get troubleshooting information: ${this.formatError(error)}`,
|
|
3213
|
+
}],
|
|
3214
|
+
isError: true,
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
async getExamples(args) {
|
|
3219
|
+
try {
|
|
3220
|
+
if (args.category === 'all') {
|
|
3221
|
+
let result = `💡 **ALL CODE EXAMPLES**\n\n`;
|
|
3222
|
+
for (const [category, examples] of Object.entries(QIK_DOCUMENTATION.examples)) {
|
|
3223
|
+
result += `## ${category.toUpperCase()}\n\n`;
|
|
3224
|
+
for (const [exampleKey, example] of Object.entries(examples)) {
|
|
3225
|
+
const typedExample = example;
|
|
3226
|
+
result += `### ${typedExample.title}\n`;
|
|
3227
|
+
result += `${typedExample.description}\n\n`;
|
|
3228
|
+
result += `\`\`\`json\n${JSON.stringify(typedExample.code, null, 2)}\n\`\`\`\n\n`;
|
|
3229
|
+
result += `**Explanation:** ${typedExample.explanation}\n\n`;
|
|
3230
|
+
if (typedExample.variations && typedExample.variations.length > 0) {
|
|
3231
|
+
result += `**Variations:**\n`;
|
|
3232
|
+
typedExample.variations.forEach((variation) => {
|
|
3233
|
+
result += `- ${variation.description}\n`;
|
|
3234
|
+
result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
|
|
3235
|
+
});
|
|
3236
|
+
result += '\n';
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return {
|
|
3241
|
+
content: [{
|
|
3242
|
+
type: 'text',
|
|
3243
|
+
text: result,
|
|
3244
|
+
}],
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
const examples = QikDocumentationHelper.getExamples(args.category, args.example);
|
|
3248
|
+
if (!examples) {
|
|
3249
|
+
return {
|
|
3250
|
+
content: [{
|
|
3251
|
+
type: 'text',
|
|
3252
|
+
text: `💡 **NO EXAMPLES FOUND**
|
|
3253
|
+
|
|
3254
|
+
No examples found for category: "${args.category}"
|
|
3255
|
+
|
|
3256
|
+
**Available example categories:**
|
|
3257
|
+
- **authentication**: Token usage and auth examples
|
|
3258
|
+
- **content_creation**: Creating different content types
|
|
3259
|
+
- **filtering**: Advanced filtering and search examples
|
|
3260
|
+
|
|
3261
|
+
**Example usage:**
|
|
3262
|
+
- \`qik_get_examples\` with category: "authentication"
|
|
3263
|
+
- \`qik_get_examples\` with category: "filtering" and example: "birthday_search"
|
|
3264
|
+
- \`qik_get_examples\` with category: "all" (shows all examples)`,
|
|
3265
|
+
}],
|
|
3266
|
+
isError: true,
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
let result = `💡 **CODE EXAMPLES: ${args.category.toUpperCase()}**\n\n`;
|
|
3270
|
+
if (args.example && examples[args.example]) {
|
|
3271
|
+
// Show specific example
|
|
3272
|
+
const example = examples[args.example];
|
|
3273
|
+
result += `### ${example.title}\n`;
|
|
3274
|
+
result += `${example.description}\n\n`;
|
|
3275
|
+
result += `\`\`\`json\n${JSON.stringify(example.code, null, 2)}\n\`\`\`\n\n`;
|
|
3276
|
+
result += `**Explanation:** ${example.explanation}\n\n`;
|
|
3277
|
+
if (example.variations && example.variations.length > 0) {
|
|
3278
|
+
result += `**Variations:**\n`;
|
|
3279
|
+
example.variations.forEach((variation) => {
|
|
3280
|
+
result += `- ${variation.description}\n`;
|
|
3281
|
+
result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
else {
|
|
3286
|
+
// Show all examples in category
|
|
3287
|
+
for (const [exampleKey, example] of Object.entries(examples)) {
|
|
3288
|
+
const typedExample = example;
|
|
3289
|
+
result += `### ${typedExample.title}\n`;
|
|
3290
|
+
result += `${typedExample.description}\n\n`;
|
|
3291
|
+
result += `\`\`\`json\n${JSON.stringify(typedExample.code, null, 2)}\n\`\`\`\n\n`;
|
|
3292
|
+
result += `**Explanation:** ${typedExample.explanation}\n\n`;
|
|
3293
|
+
if (typedExample.variations && typedExample.variations.length > 0) {
|
|
3294
|
+
result += `**Variations:**\n`;
|
|
3295
|
+
typedExample.variations.forEach((variation) => {
|
|
3296
|
+
result += `- ${variation.description}\n`;
|
|
3297
|
+
result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
|
|
3298
|
+
});
|
|
3299
|
+
result += '\n';
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
return {
|
|
3304
|
+
content: [{
|
|
3305
|
+
type: 'text',
|
|
3306
|
+
text: result,
|
|
3307
|
+
}],
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
catch (error) {
|
|
3311
|
+
return {
|
|
3312
|
+
content: [{
|
|
3313
|
+
type: 'text',
|
|
3314
|
+
text: `Failed to get examples: ${this.formatError(error)}`,
|
|
3315
|
+
}],
|
|
3316
|
+
isError: true,
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
async validateFieldPlacement(args) {
|
|
3321
|
+
try {
|
|
3322
|
+
const validation = this.validateContentType(args.contentType);
|
|
3323
|
+
if (!validation.valid) {
|
|
3324
|
+
return {
|
|
3325
|
+
content: [{
|
|
3326
|
+
type: 'text',
|
|
3327
|
+
text: validation.error,
|
|
3328
|
+
}],
|
|
3329
|
+
isError: true,
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
// Use both the documentation helper and glossary-driven validation
|
|
3333
|
+
const docValidation = QikDocumentationHelper.validateFieldPlacement(args.contentType, args.payload);
|
|
3334
|
+
const glossaryValidation = this.validateFieldPlacementFromGlossary(args.payload, args.contentType);
|
|
3335
|
+
let result = `✅ **FIELD PLACEMENT VALIDATION: ${args.contentType.toUpperCase()}**\n\n`;
|
|
3336
|
+
if (glossaryValidation.valid && docValidation.valid) {
|
|
3337
|
+
result += `🎉 **VALIDATION PASSED**\n\n`;
|
|
3338
|
+
result += `Your payload has correct field placement!\n\n`;
|
|
3339
|
+
const analysis = glossaryValidation.analysis;
|
|
3340
|
+
result += `**Field Analysis:**\n`;
|
|
3341
|
+
result += `- Root level fields: ${Object.keys(args.payload).filter(k => k !== 'data' && k !== 'meta').join(', ') || 'none'}\n`;
|
|
3342
|
+
result += `- Data object fields: ${args.payload.data ? Object.keys(args.payload.data).join(', ') : 'none'}\n`;
|
|
3343
|
+
result += `- Meta fields: ${args.payload.meta ? Object.keys(args.payload.meta).join(', ') : 'none'}\n\n`;
|
|
3344
|
+
result += `**Glossary Structure:**\n`;
|
|
3345
|
+
result += `- Expected root fields: ${Array.from(analysis.rootLevelFields).join(', ') || 'none'}\n`;
|
|
3346
|
+
result += `- Expected data fields: ${Array.from(analysis.dataObjectFields).join(', ') || 'none'}\n`;
|
|
3347
|
+
}
|
|
3348
|
+
else {
|
|
3349
|
+
result += `❌ **VALIDATION FAILED**\n\n`;
|
|
3350
|
+
const allErrors = [...(docValidation.errors || []), ...(glossaryValidation.errors || [])];
|
|
3351
|
+
if (allErrors.length > 0) {
|
|
3352
|
+
result += `**Errors Found:**\n`;
|
|
3353
|
+
allErrors.forEach(error => {
|
|
3354
|
+
result += `- ${error}\n`;
|
|
3355
|
+
});
|
|
3356
|
+
result += '\n';
|
|
3357
|
+
}
|
|
3358
|
+
if (glossaryValidation.analysis) {
|
|
3359
|
+
const analysis = glossaryValidation.analysis;
|
|
3360
|
+
result += `**Correct Structure:**\n`;
|
|
3361
|
+
result += `\`\`\`json\n`;
|
|
3362
|
+
result += `{\n`;
|
|
3363
|
+
result += ` "type": "${args.contentType}",\n`;
|
|
3364
|
+
result += ` "title": "Your Title Here",\n`;
|
|
3365
|
+
const rootFields = Array.from(analysis.rootLevelFields).filter(f => f !== 'title' && f !== '_id' && f !== 'meta');
|
|
3366
|
+
if (rootFields.length > 0) {
|
|
3367
|
+
rootFields.slice(0, 2).forEach(field => {
|
|
3368
|
+
result += ` "${field}": "value",\n`;
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
result += ` "meta": { "scopes": ["scope-id-here"] }`;
|
|
3372
|
+
if (analysis.dataObjectFields.size > 0) {
|
|
3373
|
+
result += `,\n "data": {\n`;
|
|
3374
|
+
Array.from(analysis.dataObjectFields).slice(0, 3).forEach((field, index, arr) => {
|
|
3375
|
+
result += ` "${field}": "value"${index < arr.length - 1 ? ',' : ''}\n`;
|
|
3376
|
+
});
|
|
3377
|
+
result += ` }`;
|
|
3378
|
+
}
|
|
3379
|
+
result += `\n}\n\`\`\`\n`;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
return {
|
|
3383
|
+
content: [{
|
|
3384
|
+
type: 'text',
|
|
3385
|
+
text: result,
|
|
3386
|
+
}],
|
|
3387
|
+
isError: !glossaryValidation.valid || !docValidation.valid,
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
catch (error) {
|
|
3391
|
+
return {
|
|
3392
|
+
content: [{
|
|
3393
|
+
type: 'text',
|
|
3394
|
+
text: `Failed to validate field placement: ${this.formatError(error)}`,
|
|
3395
|
+
}],
|
|
3396
|
+
isError: true,
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
2436
3400
|
async run() {
|
|
2437
3401
|
const transport = new StdioServerTransport();
|
|
2438
3402
|
await this.server.connect(transport);
|