@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.
@@ -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
- // Helper methods for field analysis and processing
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 root level for applicable content types)`,
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: 'Content-specific fields (definedFields go here). Structure varies by content type.',
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
- // Get content type info to understand field structure
2103
- const contentTypeInfo = validation.info;
2104
- let fields = [];
2105
- let definedFields = [];
2106
- let baseType = null;
2107
- // Extract fields and definedFields from the content type definition
2108
- if (contentTypeInfo.fields) {
2109
- fields = contentTypeInfo.fields;
2110
- definedFields = contentTypeInfo.definedFields || [];
2111
- baseType = contentTypeInfo.definesType;
2112
- }
2113
- else if (contentTypeInfo.definition) {
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
- if (args.data && typeof args.data === 'object') {
2139
- // Create field key maps for quick lookup
2140
- const rootFieldKeys = new Set(fields.map(f => f.key));
2141
- const dataFieldKeys = new Set(definedFields.map(f => f.key));
2142
- // Separate the provided data based on field definitions
2143
- for (const [key, value] of Object.entries(args.data)) {
2144
- if (rootFieldKeys.has(key)) {
2145
- // This field should go at root level (from 'fields')
2146
- rootLevelData[key] = value;
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
- else if (dataFieldKeys.has(key)) {
2149
- // This field should go in data object (from 'definedFields')
2150
- dataObjectData[key] = value;
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: `Successfully created ${args.type} content:\n${JSON.stringify(response.data, null, 2)}`,
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);