@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.
- package/build/src/index.d.ts +9 -17
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +173 -377
- package/build/src/index.js.map +1 -1
- package/build/src/simple-index.d.ts +36 -0
- package/build/src/simple-index.d.ts.map +1 -0
- package/build/src/simple-index.js +644 -0
- package/build/src/simple-index.js.map +1 -0
- package/package.json +1 -1
package/build/src/index.js
CHANGED
|
@@ -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
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
//
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
532
|
+
// Add the data object with dynamic properties
|
|
742
533
|
properties.data = {
|
|
743
534
|
type: 'object',
|
|
744
|
-
description: `
|
|
535
|
+
description: `DYNAMIC FIELD PLACEMENT (based on glossary):
|
|
745
536
|
|
|
746
|
-
**ROOT LEVEL FIELDS (
|
|
747
|
-
|
|
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 (
|
|
751
|
-
|
|
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
|
-
**
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
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
|
|
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}`,
|
|
2411
|
+
const response = await this.axiosInstance[method](`/content/${args.id}`, updateData);
|
|
2604
2412
|
return {
|
|
2605
2413
|
content: [{
|
|
2606
2414
|
type: 'text',
|
|
2607
|
-
text:
|
|
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
|
-
//
|
|
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 (
|
|
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
|
-
|
|
3351
|
-
if (allErrors.length > 0) {
|
|
3156
|
+
if (docValidation.errors && docValidation.errors.length > 0) {
|
|
3352
3157
|
result += `**Errors Found:**\n`;
|
|
3353
|
-
|
|
3158
|
+
docValidation.errors.forEach(error => {
|
|
3354
3159
|
result += `- ${error}\n`;
|
|
3355
3160
|
});
|
|
3356
3161
|
result += '\n';
|
|
3357
3162
|
}
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
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: !
|
|
3183
|
+
isError: !docValidation.valid,
|
|
3388
3184
|
};
|
|
3389
3185
|
}
|
|
3390
3186
|
catch (error) {
|