@qikdev/mcp 6.6.8 → 6.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,7 +56,8 @@ const QIK_ACCESS_TOKEN = process.env.QIK_ACCESS_TOKEN;
56
56
  export class QikMCPServer {
57
57
  server;
58
58
  axiosInstance;
59
- glossary = {};
59
+ glossary = {}; // Full glossary with readOnly fields (for queries)
60
+ aiGlossary = {}; // AI glossary without readOnly fields (for creates/updates)
60
61
  userSession = null;
61
62
  lastGlossaryUpdate = 0;
62
63
  GLOSSARY_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
@@ -145,29 +146,25 @@ export class QikMCPServer {
145
146
  return; // Use cached version
146
147
  }
147
148
  try {
148
- const response = await this.axiosInstance.get('/glossary');
149
- const newGlossary = (response.data || []).reduce(function (memo, definition) {
150
- // Use the key property from the definition as the glossary key
149
+ // Load AI-friendly glossary (which excludes readOnly fields)
150
+ const aiGlossaryResponse = await this.axiosInstance.get('/glossary/ai');
151
+ // Process AI-friendly glossary
152
+ const newAiGlossary = (aiGlossaryResponse.data || []).reduce(function (memo, definition) {
151
153
  if (definition.key) {
152
154
  memo[definition.key] = definition;
153
155
  }
154
156
  return memo;
155
157
  }, {});
156
- // Validate glossary structure
157
- if (typeof newGlossary === 'object' && newGlossary !== null) {
158
- this.glossary = newGlossary;
159
- this.lastGlossaryUpdate = now;
160
- this.log(`Loaded ${Object.keys(this.glossary).length} content types from glossary`);
161
- // Log available content types for debugging
162
- const contentTypes = Object.keys(this.glossary).sort();
163
- this.log(`Available content types: ${contentTypes.join(', ')}`);
164
- }
165
- else {
166
- this.log('Invalid glossary response structure - keeping existing glossary');
167
- }
158
+ this.aiGlossary = newAiGlossary;
159
+ this.glossary = newAiGlossary; // Use AI glossary as the main glossary
160
+ this.lastGlossaryUpdate = now;
161
+ this.log(`Loaded ${Object.keys(this.glossary).length} content types from AI glossary`);
162
+ // Log available content types for debugging
163
+ const contentTypes = Object.keys(this.glossary).sort();
164
+ this.log(`Available content types: ${contentTypes.join(', ')}`);
168
165
  }
169
166
  catch (error) {
170
- this.log(`Failed to load glossary: ${this.formatError(error)}`);
167
+ this.log(`Failed to load AI glossary: ${this.formatError(error)}`);
171
168
  // Don't clear existing glossary on error - keep what we have
172
169
  if (Object.keys(this.glossary).length === 0) {
173
170
  this.log('No cached glossary available - some operations may fail');
@@ -210,261 +207,6 @@ export class QikMCPServer {
210
207
  getMinimumArrayLength(field) {
211
208
  return this.isFieldArray(field) ? (field.minimum || 0) : 0;
212
209
  }
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
210
  /**
469
211
  * Validates widget-specific field values
470
212
  */
@@ -727,57 +469,83 @@ ${dataFieldsList || 'none defined'}
727
469
  return 'string';
728
470
  }
729
471
  }
472
+ /**
473
+ * Generates completely dynamic properties based on the loaded glossary
474
+ * This creates schema properties for ALL fields from ALL content types
475
+ */
730
476
  generateDynamicContentProperties() {
731
- // Generate dynamic properties that show field structure for common content types
732
477
  const properties = {};
733
- // Add common root-level fields that appear across content types
734
- const commonRootFields = ['reference', 'referenceType', 'body', 'organisation'];
735
- for (const fieldKey of commonRootFields) {
736
- properties[fieldKey] = {
737
- type: 'string',
738
- description: `${fieldKey} field (ALWAYS goes at ROOT LEVEL, never in data object)`,
739
- };
478
+ // Collect all unique fields from all content types in the glossary
479
+ const allRootFields = new Set();
480
+ const allDataFields = new Set();
481
+ const fieldSchemas = new Map();
482
+ // Process each content type in the glossary
483
+ for (const [contentTypeKey, contentType] of Object.entries(this.glossary)) {
484
+ if (!contentType || typeof contentType !== 'object')
485
+ continue;
486
+ // Get fields from the content type (these go at root level)
487
+ const fields = contentType.fields || [];
488
+ for (const field of fields) {
489
+ if (field.key && field.key !== '_id') { // Skip internal ID field
490
+ allRootFields.add(field.key);
491
+ // Generate schema for this field
492
+ if (!fieldSchemas.has(field.key)) {
493
+ fieldSchemas.set(field.key, this.generateFieldSchema(field));
494
+ }
495
+ }
496
+ }
497
+ // Get definedFields from the content type (these go in data object)
498
+ const definedFields = contentType.definedFields || [];
499
+ for (const field of definedFields) {
500
+ if (field.key) {
501
+ allDataFields.add(field.key);
502
+ // Generate schema for this field
503
+ if (!fieldSchemas.has(field.key)) {
504
+ fieldSchemas.set(field.key, this.generateFieldSchema(field));
505
+ }
506
+ }
507
+ }
508
+ }
509
+ // Add all root-level fields as properties
510
+ for (const fieldKey of allRootFields) {
511
+ if (fieldKey !== 'title' && fieldKey !== 'meta') { // These are handled separately
512
+ const schema = fieldSchemas.get(fieldKey);
513
+ if (schema) {
514
+ properties[fieldKey] = {
515
+ ...schema,
516
+ description: `${schema.description || fieldKey} (ROOT LEVEL field from glossary)`
517
+ };
518
+ }
519
+ }
520
+ }
521
+ // Create comprehensive data object with all possible data fields
522
+ const dataProperties = {};
523
+ for (const fieldKey of allDataFields) {
524
+ const schema = fieldSchemas.get(fieldKey);
525
+ if (schema) {
526
+ dataProperties[fieldKey] = {
527
+ ...schema,
528
+ description: `${schema.description || fieldKey} (DATA OBJECT field from glossary)`
529
+ };
530
+ }
740
531
  }
741
- // Add a data object for definedFields with clear explanation
532
+ // Add the data object with dynamic properties
742
533
  properties.data = {
743
534
  type: 'object',
744
- description: `CRITICAL FIELD PLACEMENT RULE:
535
+ description: `DYNAMIC FIELD PLACEMENT (based on glossary):
745
536
 
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
537
+ **ROOT LEVEL FIELDS (from glossary "fields" array):**
538
+ ${Array.from(allRootFields).sort().join(', ')}
749
539
 
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.
540
+ **DATA OBJECT FIELDS (from glossary "definedFields" array):**
541
+ ${Array.from(allDataFields).sort().join(', ')}
753
542
 
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
- }
543
+ **FIELD PLACEMENT RULE:**
544
+ - Fields in glossary "fields" array → ROOT LEVEL
545
+ - Fields in glossary "definedFields" array → DATA OBJECT
762
546
 
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
- }`,
770
- properties: {
771
- wasAnyoneHurt: {
772
- type: 'string',
773
- description: 'For incident reports - whether anyone was hurt (this is a definedField, so it goes in data object)',
774
- enum: ['yes', 'no']
775
- },
776
- whatTimeDidItHappen: {
777
- type: 'string',
778
- description: 'For incident reports - when the incident occurred (this is a definedField, so it goes in data object)'
779
- }
780
- }
547
+ The MCP server automatically determines correct placement based on the glossary definition for each content type.`,
548
+ properties: dataProperties
781
549
  };
782
550
  return properties;
783
551
  }
@@ -1033,6 +801,50 @@ ${dataFieldsList || 'none defined'}
1033
801
  }]
1034
802
  };
1035
803
  }
804
+ /**
805
+ * Filters out readOnly fields from payload for create/update operations
806
+ */
807
+ filterReadOnlyFields(payload, contentType) {
808
+ const typeInfo = this.getContentTypeInfo(contentType);
809
+ if (!typeInfo) {
810
+ return payload; // If we can't get type info, return payload as-is
811
+ }
812
+ const filteredPayload = { ...payload };
813
+ // Get fields from the content type - handle both QikField and QikAIField structures
814
+ let fields = [];
815
+ if (typeInfo.fields) {
816
+ fields = typeInfo.fields;
817
+ }
818
+ // Filter out readOnly fields from root level
819
+ for (const field of fields) {
820
+ if (field.readOnly && field.key && filteredPayload[field.key] !== undefined) {
821
+ this.log(`Filtering out readOnly field: ${field.key}`);
822
+ delete filteredPayload[field.key];
823
+ }
824
+ }
825
+ // Filter out readOnly fields from data object
826
+ if (filteredPayload.data && typeof filteredPayload.data === 'object') {
827
+ for (const field of fields) {
828
+ if (field.readOnly) {
829
+ // Handle both QikAIField (with path) and QikField (with key) structures
830
+ let dataFieldKey = null;
831
+ if (field.path && field.path.startsWith('data.')) {
832
+ // QikAIField structure
833
+ dataFieldKey = field.path.replace('data.', '');
834
+ }
835
+ else if (field.key) {
836
+ // QikField structure - assume it's a data field if it's readOnly and not at root
837
+ dataFieldKey = field.key;
838
+ }
839
+ if (dataFieldKey && filteredPayload.data[dataFieldKey] !== undefined) {
840
+ this.log(`Filtering out readOnly data field: ${dataFieldKey}`);
841
+ delete filteredPayload.data[dataFieldKey];
842
+ }
843
+ }
844
+ }
845
+ }
846
+ return filteredPayload;
847
+ }
1036
848
  generateEnhancedFilterSchema() {
1037
849
  return {
1038
850
  type: 'object',
@@ -2532,24 +2344,12 @@ ${JSON.stringify({
2532
2344
  isError: true,
2533
2345
  };
2534
2346
  }
2535
- // Use the new glossary-driven field placement validation
2536
2347
  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
- };
2547
- }
2548
- // Automatically restructure the payload based on glossary rules
2549
- const payload = this.restructurePayloadFromGlossary(args, args.type);
2348
+ // Create payload from args and filter out readOnly fields
2349
+ let payload = { ...args };
2350
+ payload = this.filterReadOnlyFields(payload, args.type);
2550
2351
  // Handle scope inheritance for comment types
2551
- const analysis = fieldValidation.analysis;
2552
- const isCommentType = analysis.baseType === 'comment' || args.type === 'comment' || args.type.includes('Comment');
2352
+ const isCommentType = args.type === 'comment' || args.type.includes('Comment');
2553
2353
  if (isCommentType && payload.reference && (!payload.meta?.scopes || payload.meta.scopes.length === 0)) {
2554
2354
  try {
2555
2355
  // Fetch the referenced item to get its scopes
@@ -2572,26 +2372,22 @@ ${JSON.stringify({
2572
2372
  type: 'text',
2573
2373
  text: `✅ **SUCCESSFULLY CREATED ${args.type.toUpperCase()} CONTENT**
2574
2374
 
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
2375
  **Created Content:**
2586
2376
  ${JSON.stringify(response.data, null, 2)}`,
2587
2377
  }],
2588
2378
  };
2589
2379
  }
2590
2380
  catch (error) {
2381
+ // Pass through server errors directly - they contain the authoritative validation messages
2591
2382
  return {
2592
2383
  content: [{
2593
2384
  type: 'text',
2594
- text: `Failed to create ${args.type}: ${this.formatError(error)}`,
2385
+ text: `Failed to create ${args.type}: ${this.formatError(error)}
2386
+
2387
+ 💡 **Field Placement Guidance:**
2388
+ - **Root Level**: title, reference, referenceType, body, organisation, meta
2389
+ - **Data Object**: Custom fields defined in content type's "definedFields" array
2390
+ - Use \`qik_get_content_definition\` to see the exact field structure for this content type`,
2595
2391
  }],
2596
2392
  isError: true,
2597
2393
  };
@@ -2599,12 +2395,27 @@ ${JSON.stringify(response.data, null, 2)}`,
2599
2395
  }
2600
2396
  async updateContent(args) {
2601
2397
  try {
2398
+ let updateData = { ...args.data };
2399
+ // Get the content type for the item being updated
2400
+ let contentType = null;
2401
+ try {
2402
+ const existingContent = await this.axiosInstance.get(`/content/${args.id}`);
2403
+ if (existingContent.data && existingContent.data.meta && existingContent.data.meta.type) {
2404
+ contentType = existingContent.data.meta.type;
2405
+ }
2406
+ }
2407
+ catch (error) {
2408
+ this.log(`Could not fetch existing content to determine type for readOnly filtering: ${this.formatError(error)}`);
2409
+ }
2602
2410
  const method = args.replace ? 'put' : 'patch';
2603
- const response = await this.axiosInstance[method](`/content/${args.id}`, args.data);
2411
+ const response = await this.axiosInstance[method](`/content/${args.id}`, updateData);
2604
2412
  return {
2605
2413
  content: [{
2606
2414
  type: 'text',
2607
- text: JSON.stringify(response.data, null, 2),
2415
+ text: `✅ **SUCCESSFULLY UPDATED CONTENT**
2416
+
2417
+ **Updated Content:**
2418
+ ${JSON.stringify(response.data, null, 2)}`,
2608
2419
  }],
2609
2420
  };
2610
2421
  }
@@ -3329,62 +3140,47 @@ No examples found for category: "${args.category}"
3329
3140
  isError: true,
3330
3141
  };
3331
3142
  }
3332
- // Use both the documentation helper and glossary-driven validation
3143
+ // Simplified validation using documentation helper only
3333
3144
  const docValidation = QikDocumentationHelper.validateFieldPlacement(args.contentType, args.payload);
3334
- const glossaryValidation = this.validateFieldPlacementFromGlossary(args.payload, args.contentType);
3335
3145
  let result = `✅ **FIELD PLACEMENT VALIDATION: ${args.contentType.toUpperCase()}**\n\n`;
3336
- if (glossaryValidation.valid && docValidation.valid) {
3146
+ if (docValidation.valid) {
3337
3147
  result += `🎉 **VALIDATION PASSED**\n\n`;
3338
3148
  result += `Your payload has correct field placement!\n\n`;
3339
- const analysis = glossaryValidation.analysis;
3340
3149
  result += `**Field Analysis:**\n`;
3341
3150
  result += `- Root level fields: ${Object.keys(args.payload).filter(k => k !== 'data' && k !== 'meta').join(', ') || 'none'}\n`;
3342
3151
  result += `- Data object fields: ${args.payload.data ? Object.keys(args.payload.data).join(', ') : 'none'}\n`;
3343
3152
  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
3153
  }
3348
3154
  else {
3349
3155
  result += `❌ **VALIDATION FAILED**\n\n`;
3350
- const allErrors = [...(docValidation.errors || []), ...(glossaryValidation.errors || [])];
3351
- if (allErrors.length > 0) {
3156
+ if (docValidation.errors && docValidation.errors.length > 0) {
3352
3157
  result += `**Errors Found:**\n`;
3353
- allErrors.forEach(error => {
3158
+ docValidation.errors.forEach(error => {
3354
3159
  result += `- ${error}\n`;
3355
3160
  });
3356
3161
  result += '\n';
3357
3162
  }
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
- }
3163
+ result += `**General Field Placement Rules:**\n`;
3164
+ result += `- **Root Level**: title, reference, referenceType, body, organisation, meta\n`;
3165
+ result += `- **Data Object**: Custom fields defined in content type's "definedFields" array\n`;
3166
+ result += `- Use \`qik_get_content_definition\` to see the exact field structure for this content type\n\n`;
3167
+ result += `**Correct Structure Example:**\n`;
3168
+ result += `\`\`\`json\n`;
3169
+ result += `{\n`;
3170
+ result += ` "type": "${args.contentType}",\n`;
3171
+ result += ` "title": "Your Title Here",\n`;
3172
+ result += ` "meta": { "scopes": ["scope-id-here"] },\n`;
3173
+ result += ` "data": {\n`;
3174
+ result += ` "customField": "value"\n`;
3175
+ result += ` }\n`;
3176
+ result += `}\n\`\`\`\n`;
3381
3177
  }
3382
3178
  return {
3383
3179
  content: [{
3384
3180
  type: 'text',
3385
3181
  text: result,
3386
3182
  }],
3387
- isError: !glossaryValidation.valid || !docValidation.valid,
3183
+ isError: !docValidation.valid,
3388
3184
  };
3389
3185
  }
3390
3186
  catch (error) {