@qikdev/mcp 6.6.9 → 6.6.11

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.
@@ -1,79 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Qik Platform MCP Server - Enhanced Version with Comprehensive Documentation
3
+ * Simplified Qik Platform MCP Server
4
4
  *
5
- * This MCP server provides comprehensive integration with the Qik platform,
6
- * enabling AI assistants to interact with Qik's content management system,
7
- * user management, forms, files, and more.
8
- *
9
- * Key Features:
10
- * - Glossary-driven architecture for dynamic content type discovery
11
- * - Smart validation based on content type definitions
12
- * - Context-aware error handling and suggestions
13
- * - Dynamic tool schema generation
14
- * - Comprehensive API coverage
15
- * - Intelligent request building and field validation
16
- * - Advanced disambiguation logic for definitions vs instances
17
- * - Workflow system documentation and automation
18
- * - Comprehensive scope and permission management
19
- *
20
- * IMPORTANT QIK CONCEPTS:
21
- *
22
- * 1. DEFINITIONS vs INSTANCES:
23
- * - Definitions: Templates that define structure (e.g., "workflow definition", "content type definition")
24
- * - Instances: Actual content items created from definitions (e.g., "workflow card", "article instance")
25
- * - When user says "create a workflow" they usually mean create a workflow DEFINITION
26
- * - When user says "add Jim to workflow X" they mean create a workflow CARD instance
27
- *
28
- * 2. WORKFLOW SYSTEM:
29
- * - Workflow Definitions: Define the structure with columns, steps, automation
30
- * - Workflow Cards: Individual items that move through the workflow
31
- * - Columns: Represent stages in the workflow (e.g., "To Do", "In Progress", "Done")
32
- * - Steps: Specific positions within columns where cards can be placed
33
- * - Automation: Entry/exit/success/fail functions that run when cards move
34
- *
35
- * 3. SCOPE SYSTEM:
36
- * - Hierarchical permission structure (like folders)
37
- * - Every content item must belong to at least one scope
38
- * - Users need appropriate permissions within scopes to perform actions
39
- * - Scopes can inherit permissions from parent scopes
40
- *
41
- * 4. CONTENT TYPE SYSTEM:
42
- * - Base types: Core Qik types (article, profile, event, etc.)
43
- * - Extended types: Custom types that extend base types with additional fields
44
- * - Fields vs DefinedFields: Fields go at root level, definedFields go in data object
5
+ * A simple translation layer between AI models and the Qik API.
6
+ * Focuses on core functionality with interactive error handling.
45
7
  */
46
8
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
47
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
48
10
  import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
49
11
  import axios from 'axios';
50
- import FormData from 'form-data';
51
12
  import { ConfigManager } from './config.js';
52
- import { QikDocumentationHelper, QIK_DOCUMENTATION } from './documentation.js';
53
13
  // Environment variables
54
14
  const QIK_API_URL = process.env.QIK_API_URL || 'https://api.qik.dev';
55
15
  const QIK_ACCESS_TOKEN = process.env.QIK_ACCESS_TOKEN;
56
16
  export class QikMCPServer {
57
17
  server;
58
18
  axiosInstance;
59
- glossary = {};
60
19
  userSession = null;
61
- lastGlossaryUpdate = 0;
62
- GLOSSARY_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
63
- serverName = 'Qik'; // Default fallback
20
+ glossary = {};
21
+ serverName = 'Qik';
64
22
  constructor() {
65
23
  if (!QIK_ACCESS_TOKEN) {
66
24
  throw new Error('QIK_ACCESS_TOKEN environment variable is required. Run "qik-mcp-server setup" to configure.');
67
25
  }
68
26
  this.server = new Server({
69
27
  name: "qik-mcp-server",
70
- version: "2.0.0",
28
+ version: "3.0.0",
71
29
  }, {
72
30
  capabilities: {
73
31
  tools: {},
74
32
  },
75
33
  });
76
- // Configure axios instance with Qik API settings
34
+ // Configure axios instance
77
35
  this.axiosInstance = axios.create({
78
36
  baseURL: QIK_API_URL,
79
37
  headers: {
@@ -92,19 +50,14 @@ export class QikMCPServer {
92
50
  });
93
51
  }
94
52
  log(message) {
95
- // Only log in development or when explicitly enabled
96
53
  if (process.env.NODE_ENV !== 'production' || process.env.QIK_MCP_DEBUG === 'true') {
97
- // Use stderr to avoid interfering with MCP JSON protocol on stdout
98
54
  process.stderr.write(`[Qik MCP] ${message}\n`);
99
55
  }
100
56
  }
101
57
  async initializeServer() {
102
58
  try {
103
- // Load server name from config first
104
59
  await this.loadServerName();
105
- // Load user session
106
60
  await this.loadUserSession();
107
- // Then load glossary
108
61
  await this.loadGlossary();
109
62
  this.log(`Initialized with ${Object.keys(this.glossary).length} content types`);
110
63
  }
@@ -118,15 +71,10 @@ export class QikMCPServer {
118
71
  const config = await configManager.loadConfig();
119
72
  if (config && config.serverName) {
120
73
  this.serverName = config.serverName;
121
- this.log(`Loaded server name: ${this.serverName}`);
122
- }
123
- else {
124
- this.log(`No server name found in config, using default: ${this.serverName}`);
125
74
  }
126
75
  }
127
76
  catch (error) {
128
- this.log(`Failed to load server name from config: ${this.formatError(error)}`);
129
- // Keep default fallback value
77
+ this.log(`Failed to load server name: ${this.formatError(error)}`);
130
78
  }
131
79
  }
132
80
  async loadUserSession() {
@@ -139,39 +87,19 @@ export class QikMCPServer {
139
87
  this.log(`Failed to load user session: ${this.formatError(error)}`);
140
88
  }
141
89
  }
142
- async loadGlossary(force = false) {
143
- const now = Date.now();
144
- if (!force && this.lastGlossaryUpdate && (now - this.lastGlossaryUpdate) < this.GLOSSARY_CACHE_TTL) {
145
- return; // Use cached version
146
- }
90
+ async loadGlossary() {
147
91
  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
92
+ const response = await this.axiosInstance.get('/glossary/ai');
93
+ this.glossary = (response.data || []).reduce((memo, definition) => {
151
94
  if (definition.key) {
152
95
  memo[definition.key] = definition;
153
96
  }
154
97
  return memo;
155
98
  }, {});
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
- }
99
+ this.log(`Loaded ${Object.keys(this.glossary).length} content types`);
168
100
  }
169
101
  catch (error) {
170
102
  this.log(`Failed to load glossary: ${this.formatError(error)}`);
171
- // Don't clear existing glossary on error - keep what we have
172
- if (Object.keys(this.glossary).length === 0) {
173
- this.log('No cached glossary available - some operations may fail');
174
- }
175
103
  }
176
104
  }
177
105
  formatError(error) {
@@ -186,472 +114,54 @@ export class QikMCPServer {
186
114
  }
187
115
  return error.message || String(error);
188
116
  }
189
- getContentTypeInfo(type) {
190
- return this.glossary[type] || null;
191
- }
192
- validateContentType(type) {
193
- const info = this.getContentTypeInfo(type);
194
- if (!info) {
195
- const availableTypes = Object.keys(this.glossary).join(', ');
196
- return {
197
- valid: false,
198
- error: `Content type '${type}' not found. Available types: ${availableTypes}`
199
- };
200
- }
201
- return { valid: true, info };
202
- }
203
- // Enhanced glossary-driven field analysis methods
204
- isFieldRequired(field) {
205
- return field.minimum !== undefined && field.minimum > 0;
206
- }
207
- isFieldArray(field) {
208
- return field.maximum === 0;
209
- }
210
- getMinimumArrayLength(field) {
211
- return this.isFieldArray(field) ? (field.minimum || 0) : 0;
212
- }
213
- /**
214
- * Validates widget-specific field values
215
- */
216
- validateWidgetValue(fieldKey, value, widgetType) {
217
- const errors = [];
218
- const warnings = [];
219
- switch (widgetType) {
220
- case 'dateobject':
221
- return this.validateDateObjectWidget(fieldKey, value);
222
- case 'date':
223
- if (typeof value === 'string') {
224
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
225
- if (!dateRegex.test(value)) {
226
- errors.push(`Field "${fieldKey}" with date widget must use ISO date format (YYYY-MM-DD), got: ${value}`);
227
- }
228
- }
229
- else {
230
- errors.push(`Field "${fieldKey}" with date widget must be a string in ISO date format (YYYY-MM-DD)`);
231
- }
232
- break;
233
- case 'time':
234
- if (typeof value === 'string') {
235
- const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;
236
- if (!timeRegex.test(value)) {
237
- errors.push(`Field "${fieldKey}" with time widget must use time format (HH:MM or HH:MM:SS), got: ${value}`);
238
- }
239
- }
240
- else {
241
- errors.push(`Field "${fieldKey}" with time widget must be a string in time format (HH:MM)`);
242
- }
243
- break;
244
- case 'datetime':
245
- if (typeof value === 'string') {
246
- try {
247
- new Date(value);
248
- }
249
- catch (e) {
250
- errors.push(`Field "${fieldKey}" with datetime widget must be a valid ISO datetime string, got: ${value}`);
251
- }
252
- }
253
- else {
254
- errors.push(`Field "${fieldKey}" with datetime widget must be a string in ISO datetime format`);
255
- }
256
- break;
257
- default:
258
- // No specific validation for other widget types
259
- break;
260
- }
261
- return { valid: errors.length === 0, errors, warnings };
262
- }
263
- /**
264
- * Validates dateobject widget values
265
- */
266
- validateDateObjectWidget(fieldKey, value) {
267
- const errors = [];
268
- const warnings = [];
269
- if (!value || typeof value !== 'object') {
270
- errors.push(`Field "${fieldKey}" with dateobject widget must be an object with hour, minute, day, month, year properties`);
271
- return { valid: false, errors, warnings };
272
- }
273
- const requiredProps = ['hour', 'minute', 'day', 'month', 'year'];
274
- const missingProps = requiredProps.filter(prop => value[prop] === undefined || value[prop] === null);
275
- if (missingProps.length > 0) {
276
- errors.push(`Field "${fieldKey}" dateobject is missing required properties: ${missingProps.join(', ')}`);
277
- }
278
- // Validate ranges
279
- if (typeof value.hour === 'number') {
280
- if (value.hour < 0 || value.hour > 23) {
281
- errors.push(`Field "${fieldKey}" dateobject hour must be between 0-23, got: ${value.hour}`);
282
- }
283
- }
284
- else if (value.hour !== undefined) {
285
- errors.push(`Field "${fieldKey}" dateobject hour must be a number`);
286
- }
287
- if (typeof value.minute === 'number') {
288
- if (value.minute < 0 || value.minute > 59) {
289
- errors.push(`Field "${fieldKey}" dateobject minute must be between 0-59, got: ${value.minute}`);
290
- }
291
- }
292
- else if (value.minute !== undefined) {
293
- errors.push(`Field "${fieldKey}" dateobject minute must be a number`);
294
- }
295
- if (typeof value.day === 'number') {
296
- if (value.day < 1 || value.day > 31) {
297
- errors.push(`Field "${fieldKey}" dateobject day must be between 1-31, got: ${value.day}`);
298
- }
299
- }
300
- else if (value.day !== undefined) {
301
- errors.push(`Field "${fieldKey}" dateobject day must be a number`);
302
- }
303
- if (typeof value.month === 'number') {
304
- if (value.month < 1 || value.month > 12) {
305
- errors.push(`Field "${fieldKey}" dateobject month must be between 1-12, got: ${value.month}`);
306
- }
307
- }
308
- else if (value.month !== undefined) {
309
- errors.push(`Field "${fieldKey}" dateobject month must be a number`);
310
- }
311
- if (typeof value.year === 'number') {
312
- if (value.year < 1900 || value.year > 2100) {
313
- warnings.push(`Field "${fieldKey}" dateobject year ${value.year} seems unusual (expected 1900-2100)`);
314
- }
315
- }
316
- else if (value.year !== undefined) {
317
- errors.push(`Field "${fieldKey}" dateobject year must be a number`);
318
- }
319
- return { valid: errors.length === 0, errors, warnings };
320
- }
321
- generateFieldPath(field, isDefinedField, groupPath = '') {
322
- let basePath = isDefinedField ? 'data' : '';
323
- if (groupPath) {
324
- basePath = basePath ? `${basePath}.${groupPath}` : groupPath;
325
- }
326
- return basePath ? `${basePath}.${field.key}` : field.key;
327
- }
328
- processGroupFields(fields, parentPath = '', isDefinedField = false) {
329
- const processedFields = [];
330
- for (const field of fields) {
331
- if (field.type === 'group' && field.fields) {
332
- if (field.asObject) {
333
- // Group creates nested object structure
334
- const groupPath = parentPath ? `${parentPath}.${field.key}` : field.key;
335
- processedFields.push(...this.processGroupFields(field.fields, groupPath, isDefinedField));
336
- }
337
- else {
338
- // Group is just for organization, fields remain at same level
339
- processedFields.push(...this.processGroupFields(field.fields, parentPath, isDefinedField));
340
- }
341
- }
342
- else {
343
- const fieldPath = this.generateFieldPath(field, isDefinedField, parentPath);
344
- processedFields.push({
345
- field,
346
- path: fieldPath,
347
- isRequired: this.isFieldRequired(field),
348
- isArray: this.isFieldArray(field)
349
- });
350
- }
351
- }
352
- return processedFields;
353
- }
354
- analyzeContentTypeFields(contentType) {
355
- let fields = [];
356
- let definedFields = [];
357
- // Extract fields from different possible structures
358
- if (contentType.fields) {
359
- fields = contentType.fields;
360
- definedFields = contentType.definedFields || [];
361
- }
362
- else if (contentType.definition) {
363
- fields = contentType.definition.fields || [];
364
- definedFields = contentType.definition.definedFields || [];
365
- }
366
- else if (contentType.type) {
367
- fields = contentType.type.fields || [];
368
- definedFields = contentType.type.definedFields || [];
369
- }
370
- const topLevelFields = this.processGroupFields(fields, '', false);
371
- const dataFields = this.processGroupFields(definedFields, '', true);
372
- const allFields = [...topLevelFields, ...dataFields];
373
- const allRequiredFields = allFields.filter(f => f.isRequired);
374
- return {
375
- topLevelFields,
376
- dataFields,
377
- allRequiredFields
378
- };
379
- }
380
- validateFieldData(data, fields, contentType) {
381
- const errors = [];
382
- for (const field of fields) {
383
- const value = data[field.key];
384
- // Check required fields
385
- if (field.minimum && field.minimum > 0 && (!value || (Array.isArray(value) && value.length === 0))) {
386
- errors.push(`Field '${field.key}' (${field.title}) is required for ${contentType}`);
387
- }
388
- // Type validation
389
- if (value !== undefined && value !== null) {
390
- switch (field.type) {
391
- case 'string':
392
- if (typeof value !== 'string') {
393
- errors.push(`Field '${field.key}' must be a string, got ${typeof value}`);
394
- }
395
- break;
396
- case 'number':
397
- case 'integer':
398
- if (typeof value !== 'number') {
399
- errors.push(`Field '${field.key}' must be a number, got ${typeof value}`);
400
- }
401
- break;
402
- case 'boolean':
403
- if (typeof value !== 'boolean') {
404
- errors.push(`Field '${field.key}' must be a boolean, got ${typeof value}`);
405
- }
406
- break;
407
- case 'array':
408
- if (!Array.isArray(value)) {
409
- errors.push(`Field '${field.key}' must be an array, got ${typeof value}`);
410
- }
411
- break;
412
- case 'reference':
413
- if (field.referenceType && typeof value === 'string') {
414
- // Basic validation - could be enhanced to check if referenced item exists
415
- }
416
- else if (Array.isArray(value) && field.maximum !== 1) {
417
- // Array of references
418
- }
419
- else {
420
- errors.push(`Field '${field.key}' must be a valid reference to ${field.referenceType || 'content'}`);
421
- }
422
- break;
423
- }
424
- }
425
- }
426
- return { valid: errors.length === 0, errors };
427
- }
428
- generateFieldSchema(field) {
429
- const schema = {
430
- type: this.mapFieldTypeToJsonSchema(field.type),
431
- description: field.description || field.title,
432
- };
433
- if (field.options && field.options.length > 0) {
434
- schema.enum = field.options.map(opt => opt.value);
435
- }
436
- if (field.type === 'array' && field.fields) {
437
- schema.items = {
438
- type: 'object',
439
- properties: this.generateFieldsSchema(field.fields),
440
- };
441
- }
442
- if (field.type === 'reference') {
443
- schema.description += field.referenceType ? ` (references ${field.referenceType})` : ' (content reference)';
444
- }
445
- return schema;
446
- }
447
- generateFieldsSchema(fields) {
448
- const properties = {};
449
- for (const field of fields) {
450
- properties[field.key] = this.generateFieldSchema(field);
451
- }
452
- return properties;
453
- }
454
- mapFieldTypeToJsonSchema(fieldType) {
455
- switch (fieldType) {
456
- case 'string':
457
- case 'email':
458
- case 'url':
459
- case 'date':
460
- return 'string';
461
- case 'number':
462
- case 'integer':
463
- return 'number';
464
- case 'boolean':
465
- return 'boolean';
466
- case 'array':
467
- return 'array';
468
- case 'group':
469
- case 'reference':
470
- return 'object';
471
- default:
472
- return 'string';
473
- }
474
- }
475
- /**
476
- * Generates completely dynamic properties based on the loaded glossary
477
- * This creates schema properties for ALL fields from ALL content types
478
- */
479
- generateDynamicContentProperties() {
480
- const properties = {};
481
- // Collect all unique fields from all content types in the glossary
482
- const allRootFields = new Set();
483
- const allDataFields = new Set();
484
- const fieldSchemas = new Map();
485
- // Process each content type in the glossary
486
- for (const [contentTypeKey, contentType] of Object.entries(this.glossary)) {
487
- if (!contentType || typeof contentType !== 'object')
488
- continue;
489
- // Get fields from the content type (these go at root level)
490
- const fields = contentType.fields || [];
491
- for (const field of fields) {
492
- if (field.key && field.key !== '_id') { // Skip internal ID field
493
- allRootFields.add(field.key);
494
- // Generate schema for this field
495
- if (!fieldSchemas.has(field.key)) {
496
- fieldSchemas.set(field.key, this.generateFieldSchema(field));
497
- }
498
- }
499
- }
500
- // Get definedFields from the content type (these go in data object)
501
- const definedFields = contentType.definedFields || [];
502
- for (const field of definedFields) {
503
- if (field.key) {
504
- allDataFields.add(field.key);
505
- // Generate schema for this field
506
- if (!fieldSchemas.has(field.key)) {
507
- fieldSchemas.set(field.key, this.generateFieldSchema(field));
508
- }
509
- }
510
- }
511
- }
512
- // Add all root-level fields as properties
513
- for (const fieldKey of allRootFields) {
514
- if (fieldKey !== 'title' && fieldKey !== 'meta') { // These are handled separately
515
- const schema = fieldSchemas.get(fieldKey);
516
- if (schema) {
517
- properties[fieldKey] = {
518
- ...schema,
519
- description: `${schema.description || fieldKey} (ROOT LEVEL field from glossary)`
520
- };
521
- }
522
- }
523
- }
524
- // Create comprehensive data object with all possible data fields
525
- const dataProperties = {};
526
- for (const fieldKey of allDataFields) {
527
- const schema = fieldSchemas.get(fieldKey);
528
- if (schema) {
529
- dataProperties[fieldKey] = {
530
- ...schema,
531
- description: `${schema.description || fieldKey} (DATA OBJECT field from glossary)`
532
- };
117
+ async promptUserForScopes() {
118
+ try {
119
+ const response = await this.axiosInstance.get('/scope/tree');
120
+ const scopes = this.extractAvailableScopes(response.data);
121
+ if (scopes.length === 0) {
122
+ throw new Error('No scopes available for content creation');
533
123
  }
534
- }
535
- // Add the data object with dynamic properties
536
- properties.data = {
537
- type: 'object',
538
- description: `DYNAMIC FIELD PLACEMENT (based on glossary):
539
-
540
- **ROOT LEVEL FIELDS (from glossary "fields" array):**
541
- ${Array.from(allRootFields).sort().join(', ')}
124
+ return {
125
+ content: [{
126
+ type: 'text',
127
+ text: `🔐 **SCOPE SELECTION REQUIRED**
542
128
 
543
- **DATA OBJECT FIELDS (from glossary "definedFields" array):**
544
- ${Array.from(allDataFields).sort().join(', ')}
129
+ To create content, you need to specify which scope(s) to create it in.
545
130
 
546
- **FIELD PLACEMENT RULE:**
547
- - Fields in glossary "fields" array → ROOT LEVEL
548
- - Fields in glossary "definedFields" array → DATA OBJECT
131
+ **Available Scopes:**
132
+ ${scopes.map(s => `- **${s.id}**: ${s.title} (${s.path})`).join('\n')}
549
133
 
550
- The MCP server automatically determines correct placement based on the glossary definition for each content type.`,
551
- properties: dataProperties
552
- };
553
- return properties;
554
- }
555
- findContentTypesByDescription(description) {
556
- const normalizedDescription = description.toLowerCase().trim();
557
- const matches = [];
558
- // Direct match first (check if the description exactly matches a key)
559
- if (this.glossary[normalizedDescription]) {
560
- return [normalizedDescription];
561
- }
562
- // Search through glossary for matches
563
- for (const [key, contentType] of Object.entries(this.glossary)) {
564
- if (!contentType || typeof contentType !== 'object')
565
- continue;
566
- // Get title and plural - the glossary structure from the screenshot shows these are direct properties
567
- let title = '';
568
- let plural = '';
569
- // Based on the screenshot, the structure is directly on the contentType object
570
- if (contentType.title) {
571
- title = (contentType.title || '').toLowerCase();
572
- plural = (contentType.plural || '').toLowerCase();
573
- }
574
- // Fallback to nested structures if they exist
575
- else if (contentType.definition) {
576
- title = (contentType.definition.title || '').toLowerCase();
577
- plural = (contentType.definition.plural || '').toLowerCase();
578
- }
579
- else if (contentType.type) {
580
- title = (contentType.type.title || '').toLowerCase();
581
- plural = (contentType.type.plural || '').toLowerCase();
582
- }
583
- // Exact title/plural match (e.g., "incident report" matches "Incident Report")
584
- if (title === normalizedDescription || plural === normalizedDescription) {
585
- matches.push(key);
586
- continue;
587
- }
588
- // Partial word matches - check if all words in description are found in title
589
- const descriptionWords = normalizedDescription.split(/\s+/);
590
- const titleWords = title.split(/\s+/);
591
- if (descriptionWords.length > 0 && descriptionWords.every(word => titleWords.some(titleWord => titleWord.includes(word) || word.includes(titleWord)))) {
592
- matches.push(key);
593
- continue;
594
- }
595
- // Also check against the key itself for partial matches
596
- const keyLower = key.toLowerCase();
597
- if (descriptionWords.every(word => keyLower.includes(word))) {
598
- matches.push(key);
599
- continue;
600
- }
601
- // Specific common term matches
602
- if (normalizedDescription.includes('incident') && (title.includes('incident') || keyLower.includes('incident'))) {
603
- matches.push(key);
604
- }
605
- else if (normalizedDescription.includes('report') && (title.includes('report') || keyLower.includes('report'))) {
606
- matches.push(key);
607
- }
608
- else if (normalizedDescription.includes('event') && (title.includes('event') || keyLower.includes('event'))) {
609
- matches.push(key);
610
- }
611
- else if (normalizedDescription.includes('comment') && (title.includes('comment') || keyLower.includes('comment'))) {
612
- matches.push(key);
613
- }
614
- else if (normalizedDescription.includes('issue') && (title.includes('issue') || keyLower.includes('issue'))) {
615
- matches.push(key);
616
- }
617
- else if (normalizedDescription.includes('ticket') && (title.includes('ticket') || keyLower.includes('ticket'))) {
618
- matches.push(key);
619
- }
620
- else if (normalizedDescription.includes('case') && (title.includes('case') || keyLower.includes('case'))) {
621
- matches.push(key);
622
- }
623
- }
624
- return matches;
625
- }
626
- findContentTypeByDescription(description) {
627
- const matches = this.findContentTypesByDescription(description);
628
- return matches.length === 1 ? matches[0] : null;
629
- }
630
- async getAvailableScopes() {
631
- try {
632
- const response = await this.axiosInstance.get('/scope/tree');
633
- return response.data;
134
+ Please retry your request with the scope ID(s) you want to use in the meta.scopes field.
135
+
136
+ **Example:**
137
+ \`\`\`json
138
+ {
139
+ "meta": {
140
+ "scopes": ["${scopes[0].id}"]
141
+ }
142
+ }
143
+ \`\`\``,
144
+ }],
145
+ isError: true,
146
+ };
634
147
  }
635
148
  catch (error) {
636
- this.log(`Failed to get scopes: ${this.formatError(error)}`);
637
- return null;
149
+ throw new Error(`Failed to get available scopes: ${this.formatError(error)}`);
638
150
  }
639
151
  }
640
- extractScopesWithPermissions(scopeTree, permission = 'create') {
152
+ extractAvailableScopes(scopeTree) {
641
153
  const scopes = [];
642
154
  const traverse = (node, path = '') => {
643
155
  if (!node)
644
156
  return;
645
157
  const currentPath = path ? `${path} > ${node.title || node.name || node._id}` : (node.title || node.name || node._id);
646
- // Check if user has create permission for this scope
647
- if (node.permissions && node.permissions[permission]) {
158
+ if (node.permissions && node.permissions.create) {
648
159
  scopes.push({
649
160
  id: node._id,
650
161
  title: node.title || node.name || node._id,
651
162
  path: currentPath
652
163
  });
653
164
  }
654
- // Traverse children
655
165
  if (node.children && Array.isArray(node.children)) {
656
166
  for (const child of node.children) {
657
167
  traverse(child, currentPath);
@@ -668,812 +178,94 @@ The MCP server automatically determines correct placement based on the glossary
668
178
  }
669
179
  return scopes;
670
180
  }
671
- // Enhanced filter helper functions
672
- validateFilter(filter) {
673
- const errors = [];
674
- if (!filter || typeof filter !== 'object') {
675
- return { valid: true, errors: [] }; // Empty filter is valid
676
- }
677
- // Check if it's a filter group or condition
678
- if (filter.operator) {
679
- // Filter group validation
680
- if (!['and', 'or', 'nor'].includes(filter.operator)) {
681
- errors.push(`Invalid operator '${filter.operator}'. Must be 'and', 'or', or 'nor'.`);
682
- }
683
- if (!Array.isArray(filter.filters)) {
684
- errors.push('Filter group must have a "filters" array.');
685
- }
686
- else {
687
- // Recursively validate nested filters
688
- for (const nestedFilter of filter.filters) {
689
- const nestedValidation = this.validateFilter(nestedFilter);
690
- errors.push(...nestedValidation.errors);
691
- }
692
- }
693
- }
694
- else if (filter.key && filter.comparator) {
695
- // Filter condition validation
696
- const validComparators = [
697
- 'dateanniversary', 'anniversarybetween', 'anniversarynext', 'anniversarypast',
698
- 'datenext', 'datenotbetween', 'datenotnext', 'datebefore', 'dateafter',
699
- 'datetoday', 'datenottoday', 'datebeforetoday', 'dateaftertoday',
700
- 'datebeforenow', 'dateafternow', 'datepast', 'datenotpast',
701
- 'datesameday', 'datesamemonth', 'datesameweek', 'datesameyear',
702
- 'datebetween', 'datemonth',
703
- 'equal', 'notequal', 'in', 'notin', 'startswith', 'doesnotstartwith',
704
- 'endswith', 'doesnotendwith', 'contains', 'excludes',
705
- 'greater', 'lesser', 'greaterequal', 'lesserequal',
706
- 'notgreater', 'notlesser', 'notgreaterequal', 'notlesserequal',
707
- 'between', 'notbetween',
708
- 'valuesgreater', 'valueslesser', 'valuesgreaterequal', 'valueslesserequal',
709
- 'empty', 'notempty'
710
- ];
711
- if (!validComparators.includes(filter.comparator)) {
712
- errors.push(`Invalid comparator '${filter.comparator}'.`);
713
- }
714
- // Validate required values for specific comparators
715
- const requiresValue = ['equal', 'notequal', 'greater', 'lesser', 'greaterequal', 'lesserequal', 'contains', 'excludes', 'startswith', 'endswith', 'datebefore', 'dateafter', 'dateanniversary'];
716
- const requiresValues = ['in', 'notin'];
717
- const requiresValue2 = ['between', 'notbetween', 'anniversarybetween', 'anniversarynext', 'anniversarypast', 'datenext', 'datepast'];
718
- if (requiresValue.includes(filter.comparator) && filter.value === undefined) {
719
- errors.push(`Comparator '${filter.comparator}' requires a 'value' parameter.`);
720
- }
721
- if (requiresValues.includes(filter.comparator) && (!filter.values || !Array.isArray(filter.values))) {
722
- errors.push(`Comparator '${filter.comparator}' requires a 'values' array parameter.`);
723
- }
724
- if (requiresValue2.includes(filter.comparator) && filter.value2 === undefined) {
725
- errors.push(`Comparator '${filter.comparator}' requires both 'value' and 'value2' parameters.`);
726
- }
727
- }
728
- else {
729
- errors.push('Filter must be either a filter group (with operator and filters) or a condition (with key and comparator).');
730
- }
731
- return { valid: errors.length === 0, errors };
732
- }
733
- createBirthdayFilter(timeframe, amount, unit = 'days') {
734
- return {
735
- operator: 'and',
736
- filters: [{
737
- key: 'dob',
738
- comparator: timeframe === 'next' ? 'anniversarynext' : 'anniversarypast',
739
- value: amount,
740
- value2: unit
741
- }]
742
- };
743
- }
744
- createDateRangeFilter(field, startDate, endDate) {
745
- return {
746
- operator: 'and',
747
- filters: [{
748
- key: field,
749
- comparator: 'datebetween',
750
- value: startDate,
751
- value2: endDate
752
- }]
753
- };
754
- }
755
- createGenderFilter(gender) {
756
- return {
757
- key: 'gender',
758
- comparator: 'equal',
759
- value: gender
760
- };
761
- }
762
- createAgeRangeFilter(minAge, maxAge) {
763
- const currentYear = new Date().getFullYear();
764
- return {
765
- operator: 'and',
766
- filters: [{
767
- key: 'dobYear',
768
- comparator: 'between',
769
- value: currentYear - maxAge,
770
- value2: currentYear - minAge
771
- }]
772
- };
773
- }
774
- createThisMonthBirthdayFilter() {
775
- const now = new Date();
776
- const currentMonth = now.getMonth() + 1; // JavaScript months are 0-indexed
777
- return {
778
- operator: 'and',
779
- filters: [{
780
- key: 'dobMonth',
781
- comparator: 'equal',
782
- value: currentMonth
783
- }]
784
- };
785
- }
786
- createRecentContentFilter(days = 30) {
787
- return {
788
- operator: 'and',
789
- filters: [{
790
- key: 'meta.created',
791
- comparator: 'datepast',
792
- value: days,
793
- value2: 'days'
794
- }]
795
- };
796
- }
797
- createScopeFilter(scopeIds) {
798
- return {
799
- operator: 'and',
800
- filters: [{
801
- key: 'meta.scopes',
802
- comparator: 'in',
803
- values: scopeIds
804
- }]
805
- };
806
- }
807
- generateEnhancedFilterSchema() {
808
- return {
809
- type: 'object',
810
- description: `Advanced filter criteria using Qik's powerful filter syntax. Supports hierarchical filters with 'and', 'or', 'nor' operators and 40+ comparators for dates, strings, numbers, and arrays.
811
-
812
- EXAMPLES:
813
-
814
- 1. Birthdays in next 10 days:
815
- {
816
- "operator": "and",
817
- "filters": [{
818
- "key": "dob",
819
- "comparator": "anniversarynext",
820
- "value": 10,
821
- "value2": "days"
822
- }]
823
- }
824
-
825
- 2. Male profiles born this month:
826
- {
827
- "operator": "and",
828
- "filters": [
829
- {"key": "gender", "comparator": "equal", "value": "male"},
830
- {"key": "dobMonth", "comparator": "equal", "value": 8}
831
- ]
832
- }
833
-
834
- 3. Content created in last 30 days:
835
- {
836
- "operator": "and",
837
- "filters": [{
838
- "key": "meta.created",
839
- "comparator": "datepast",
840
- "value": 30,
841
- "value2": "days"
842
- }]
843
- }
844
-
845
- 4. Complex query with OR logic:
846
- {
847
- "operator": "or",
848
- "filters": [
849
- {"key": "firstName", "comparator": "startswith", "value": "John"},
850
- {"key": "lastName", "comparator": "contains", "value": "Smith"}
851
- ]
852
- }`,
853
- properties: {
854
- operator: {
855
- type: 'string',
856
- enum: ['and', 'or', 'nor'],
857
- description: 'Logical operator: "and" (all must match), "or" (any can match), "nor" (none can match)'
858
- },
859
- filters: {
860
- type: 'array',
861
- description: 'Array of filter conditions or nested filter groups',
862
- items: {
863
- oneOf: [
864
- {
865
- type: 'object',
866
- description: 'Filter condition',
867
- properties: {
868
- key: {
869
- type: 'string',
870
- description: 'Field path to filter on (e.g., "firstName", "meta.created", "data.customField")'
871
- },
872
- comparator: {
873
- type: 'string',
874
- enum: [
875
- // Date/Anniversary comparators
876
- 'dateanniversary', 'anniversarybetween', 'anniversarynext', 'anniversarypast',
877
- 'datenext', 'datenotbetween', 'datenotnext', 'datebefore', 'dateafter',
878
- 'datetoday', 'datenottoday', 'datebeforetoday', 'dateaftertoday',
879
- 'datebeforenow', 'dateafternow', 'datepast', 'datenotpast',
880
- 'datesameday', 'datesamemonth', 'datesameweek', 'datesameyear',
881
- 'datebetween', 'datemonth',
882
- // String comparators
883
- 'equal', 'notequal', 'in', 'notin', 'startswith', 'doesnotstartwith',
884
- 'endswith', 'doesnotendwith', 'contains', 'excludes',
885
- // Numeric comparators
886
- 'greater', 'lesser', 'greaterequal', 'lesserequal',
887
- 'notgreater', 'notlesser', 'notgreaterequal', 'notlesserequal',
888
- 'between', 'notbetween',
889
- // Array/value comparators
890
- 'valuesgreater', 'valueslesser', 'valuesgreaterequal', 'valueslesserequal',
891
- 'empty', 'notempty'
892
- ],
893
- description: 'Comparison operator - see documentation for full list and usage'
894
- },
895
- value: {
896
- description: 'Primary comparison value (type depends on comparator)'
897
- },
898
- value2: {
899
- description: 'Secondary value for range comparators (between, anniversarynext, etc.)'
900
- },
901
- values: {
902
- type: 'array',
903
- description: 'Array of values for "in" and "notin" comparators'
904
- }
905
- },
906
- required: ['key', 'comparator']
907
- },
908
- {
909
- type: 'object',
910
- description: 'Nested filter group',
911
- properties: {
912
- operator: { type: 'string', enum: ['and', 'or', 'nor'] },
913
- filters: { type: 'array' }
914
- },
915
- required: ['operator', 'filters']
916
- }
917
- ]
918
- }
919
- }
181
+ async handleApiError(error, operation, args) {
182
+ if (error.response?.status === 400) {
183
+ const errorData = error.response.data;
184
+ // Handle missing scopes
185
+ if (errorData.message && errorData.message.includes('scope')) {
186
+ return await this.promptUserForScopes();
920
187
  }
921
- };
922
- }
923
- /**
924
- * Enhanced intelligent content creation with advanced disambiguation logic
925
- *
926
- * This method provides sophisticated analysis of user intent to distinguish between:
927
- * - Creating workflow DEFINITIONS vs workflow CARD instances
928
- * - Creating content type DEFINITIONS vs content INSTANCES
929
- * - Understanding context clues like "add person to workflow" vs "create new workflow"
930
- */
931
- async intelligentContentCreation(description, additionalData) {
932
- // STEP 1: Advanced Intent Analysis with Disambiguation Logic
933
- const intentAnalysis = this.analyzeUserIntent(description, additionalData);
934
- // Handle workflow-specific disambiguation
935
- if (intentAnalysis.isWorkflowRelated) {
936
- return await this.handleWorkflowDisambiguation(description, additionalData, intentAnalysis);
937
- }
938
- // STEP 2: Standard content type matching
939
- const contentTypeMatches = this.findContentTypesByDescription(description);
940
- if (contentTypeMatches.length === 0) {
941
- return await this.handleNoContentTypeMatches(description);
942
- }
943
- if (contentTypeMatches.length > 1) {
944
- return await this.handleMultipleContentTypeMatches(description, contentTypeMatches);
945
- }
946
- // STEP 3: Single match found - provide comprehensive guidance
947
- const contentType = contentTypeMatches[0];
948
- return await this.handleSingleContentTypeMatch(contentType, description, additionalData);
949
- }
950
- /**
951
- * Analyzes user intent to distinguish between different types of content creation
952
- */
953
- analyzeUserIntent(description, additionalData) {
954
- const normalizedDesc = description.toLowerCase().trim();
955
- const contextClues = [];
956
- // Workflow-related keywords
957
- const workflowKeywords = ['workflow', 'kanban', 'board', 'column', 'step', 'process', 'pipeline'];
958
- const isWorkflowRelated = workflowKeywords.some(keyword => normalizedDesc.includes(keyword));
959
- // Definition creation indicators
960
- const definitionIndicators = [
961
- 'create a new', 'create new', 'make a new', 'design a', 'set up a', 'build a',
962
- 'define a', 'establish a', 'configure a'
963
- ];
964
- const isDefinitionCreation = definitionIndicators.some(indicator => normalizedDesc.includes(indicator));
965
- // Instance creation indicators
966
- const instanceIndicators = [
967
- 'add', 'assign', 'put', 'move', 'place', 'insert', 'include'
968
- ];
969
- const isInstanceCreation = instanceIndicators.some(indicator => normalizedDesc.includes(indicator));
970
- // Person assignment indicators
971
- const personIndicators = [
972
- 'add person', 'assign person', 'add user', 'assign user', 'add someone', 'assign someone',
973
- 'add jim', 'add john', 'add sarah', 'put person', 'move person'
974
- ];
975
- const isPersonAssignment = personIndicators.some(indicator => normalizedDesc.includes(indicator));
976
- // Collect context clues
977
- if (isWorkflowRelated)
978
- contextClues.push('workflow-related');
979
- if (isDefinitionCreation)
980
- contextClues.push('definition-creation');
981
- if (isInstanceCreation)
982
- contextClues.push('instance-creation');
983
- if (isPersonAssignment)
984
- contextClues.push('person-assignment');
985
- // Calculate confidence based on clarity of intent
986
- let confidence = 0.5; // Base confidence
987
- if (isDefinitionCreation && !isInstanceCreation)
988
- confidence = 0.9;
989
- if (isInstanceCreation && !isDefinitionCreation)
990
- confidence = 0.9;
991
- if (isPersonAssignment)
992
- confidence = 0.95;
993
- return {
994
- isWorkflowRelated,
995
- isDefinitionCreation,
996
- isInstanceCreation,
997
- isPersonAssignment,
998
- confidence,
999
- contextClues
1000
- };
1001
- }
1002
- /**
1003
- * Handles workflow-specific disambiguation with comprehensive guidance
1004
- */
1005
- async handleWorkflowDisambiguation(description, additionalData, intentAnalysis) {
1006
- const normalizedDesc = description.toLowerCase().trim();
1007
- // Check if user wants to create a workflow DEFINITION
1008
- if (intentAnalysis.isDefinitionCreation ||
1009
- normalizedDesc.includes('create a workflow') ||
1010
- normalizedDesc.includes('new workflow') ||
1011
- normalizedDesc.includes('design workflow')) {
188
+ // Handle validation errors - let user know what went wrong
1012
189
  return {
1013
190
  content: [{
1014
191
  type: 'text',
1015
- text: `🔧 **WORKFLOW DEFINITION CREATION**
1016
-
1017
- You want to create a new workflow definition (template). This defines the structure, columns, steps, and automation rules.
1018
-
1019
- **WORKFLOW DEFINITION STRUCTURE:**
192
+ text: `❌ **VALIDATION ERROR**
1020
193
 
1021
- A workflow definition includes:
1022
- - **Columns**: Stages like "To Do", "In Progress", "Review", "Done"
1023
- - **Steps**: Specific positions within columns where cards can be placed
1024
- - **Automation**: Functions that run when cards enter/exit steps
1025
- - **Due Date Behavior**: How due dates are calculated and managed
1026
- - **Completion Criteria**: Rules that determine when workflow is complete
194
+ The API rejected your request for ${operation}:
1027
195
 
1028
- **EXAMPLE WORKFLOW DEFINITION:**
1029
- \`\`\`json
1030
- {
1031
- "type": "definition",
1032
- "title": "New Student Induction Workflow",
1033
- "definesType": "workflowcard",
1034
- "workflow": [
1035
- {
1036
- "title": "Enrollment",
1037
- "description": "Initial enrollment and documentation",
1038
- "steps": [
1039
- {
1040
- "title": "Application Received",
1041
- "type": "step",
1042
- "description": "Student application has been received",
1043
- "duration": 1440,
1044
- "assignees": [],
1045
- "entryFunction": "// Code to run when card enters this step",
1046
- "exitFunction": "// Code to run when card exits this step"
1047
- }
1048
- ]
1049
- },
1050
- {
1051
- "title": "Processing",
1052
- "description": "Review and approval process",
1053
- "steps": [
1054
- {
1055
- "title": "Document Review",
1056
- "type": "step",
1057
- "description": "Review all submitted documents"
1058
- }
1059
- ]
1060
- }
1061
- ]
1062
- }
1063
- \`\`\`
196
+ **Error Details:**
197
+ ${JSON.stringify(errorData, null, 2)}
1064
198
 
1065
- To create this workflow definition, use:
1066
- \`qik_create_content\` with type: "definition" and the workflow structure in the data field.
199
+ **Your Request:**
200
+ ${JSON.stringify(args, null, 2)}
1067
201
 
1068
- Would you like me to help you create a specific workflow definition?`,
202
+ **Suggestions:**
203
+ - Check that all required fields are provided
204
+ - Verify field names match the content type definition
205
+ - Ensure scope IDs are valid and you have permissions
206
+ - Use \`qik_get_content_definition\` to see the exact field structure`,
1069
207
  }],
208
+ isError: true,
1070
209
  };
1071
210
  }
1072
- // Check if user wants to add someone to an existing workflow (create workflow CARD)
1073
- if (intentAnalysis.isPersonAssignment ||
1074
- normalizedDesc.includes('add') && normalizedDesc.includes('to workflow') ||
1075
- normalizedDesc.includes('assign') && normalizedDesc.includes('workflow')) {
211
+ if (error.response?.status === 403) {
1076
212
  return {
1077
213
  content: [{
1078
214
  type: 'text',
1079
- text: `👤 **WORKFLOW CARD CREATION (Person Assignment)**
1080
-
1081
- You want to add a person to an existing workflow by creating a workflow card instance.
215
+ text: `🚫 **PERMISSION DENIED**
1082
216
 
1083
- **WORKFLOW CARD vs WORKFLOW DEFINITION:**
1084
- - **Workflow Definition**: The template/structure (columns, steps, rules)
1085
- - **Workflow Card**: Individual items that move through the workflow
217
+ You don't have permission to perform ${operation}.
1086
218
 
1087
- **TO ADD SOMEONE TO A WORKFLOW:**
219
+ **Possible causes:**
220
+ - Your access token doesn't have the required permissions
221
+ - The scope you're trying to access is restricted
222
+ - The content type requires special permissions
1088
223
 
1089
- 1. **Find the workflow definition ID** first using:
1090
- \`qik_list_content\` with type: "definition" and search for your workflow name
1091
-
1092
- 2. **Create a workflow card** using:
1093
- \`qik_create_content\` with type: "workflowcard"
1094
-
1095
- **EXAMPLE WORKFLOW CARD:**
1096
- \`\`\`json
1097
- {
1098
- "type": "workflowcard",
1099
- "title": "John Smith - Student Induction",
1100
- "reference": "PROFILE_ID_HERE",
1101
- "referenceType": "profile",
1102
- "data": {
1103
- "workflowDefinition": "WORKFLOW_DEFINITION_ID_HERE",
1104
- "currentStep": "application-received",
1105
- "assignedTo": ["USER_ID_HERE"],
1106
- "dueDate": "2024-01-15T09:00:00.000Z"
1107
- }
1108
- }
1109
- \`\`\`
1110
-
1111
- **NEED MORE HELP?**
1112
- - What's the name of the workflow you want to add someone to?
1113
- - Who do you want to add to the workflow?
1114
- - Do you have the workflow definition ID?`,
224
+ **Next steps:**
225
+ - Check your token permissions with your administrator
226
+ - Try using \`qik_get_scopes\` to see available scopes
227
+ - Verify you have create/update permissions for this content type`,
1115
228
  }],
229
+ isError: true,
1116
230
  };
1117
231
  }
1118
- // General workflow guidance
1119
- return {
1120
- content: [{
1121
- type: 'text',
1122
- text: `🔄 **WORKFLOW SYSTEM GUIDANCE**
1123
-
1124
- I detected you're working with workflows. Please clarify your intent:
1125
-
1126
- **OPTION 1: Create Workflow Definition (Template)**
1127
- - "Create a new workflow"
1128
- - "Design a student onboarding workflow"
1129
- - "Set up a project management workflow"
1130
- → Creates the structure, columns, steps, and rules
1131
-
1132
- **OPTION 2: Add Person to Existing Workflow**
1133
- - "Add Jim to the student workflow"
1134
- - "Assign Sarah to project workflow"
1135
- - "Put John in the onboarding process"
1136
- → Creates a workflow card instance for a person
1137
-
1138
- **WORKFLOW CONCEPTS:**
1139
- - **Definition**: The template (like a Kanban board layout)
1140
- - **Card**: Individual items moving through the workflow
1141
- - **Columns**: Stages (To Do, In Progress, Done)
1142
- - **Steps**: Specific positions within columns
1143
- - **Automation**: Code that runs when cards move
1144
-
1145
- **AVAILABLE WORKFLOW CONTENT TYPES:**
1146
- - \`definition\`: For creating workflow templates
1147
- - \`workflowcard\`: For individual workflow instances
1148
- - \`object\`: For custom workflow-related objects
1149
-
1150
- Please specify: Are you creating a new workflow template, or adding someone to an existing workflow?`,
1151
- }],
1152
- };
1153
- }
1154
- /**
1155
- * Handles cases where no content types match the description
1156
- */
1157
- async handleNoContentTypeMatches(description) {
1158
- const availableTypes = Object.entries(this.glossary).map(([key, type]) => {
1159
- let title = 'Unknown';
1160
- let plural = 'Unknown';
1161
- if (type && typeof type === 'object') {
1162
- if (type.definition) {
1163
- title = type.definition.title || title;
1164
- plural = type.definition.plural || plural;
1165
- }
1166
- else if (type.type) {
1167
- title = type.type.title || title;
1168
- plural = type.type.plural || plural;
1169
- }
1170
- else if (type.title) {
1171
- title = type.title || title;
1172
- plural = type.plural || plural;
1173
- }
1174
- }
1175
- return { key, title, plural };
1176
- });
1177
- // Group types by category for better organization
1178
- const categorizedTypes = this.categorizeContentTypes(availableTypes);
232
+ // Generic error handling
1179
233
  return {
1180
234
  content: [{
1181
235
  type: 'text',
1182
- text: `❌ **NO MATCHING CONTENT TYPE FOUND**
1183
-
1184
- I couldn't find a content type for "${description}".
236
+ text: `❌ **API ERROR**
1185
237
 
1186
- **AVAILABLE CONTENT TYPES BY CATEGORY:**
238
+ Failed to ${operation}:
1187
239
 
1188
- ${categorizedTypes}
240
+ **Error:** ${this.formatError(error)}
1189
241
 
1190
- **SUGGESTIONS:**
1191
- - Try using more specific terms (e.g., "incident report" instead of "report")
1192
- - Check if you meant to create a workflow definition or workflow card
1193
- - Use the exact content type key from the list above
242
+ **Your Request:**
243
+ ${JSON.stringify(args, null, 2)}
1194
244
 
1195
- **NEED HELP?**
1196
- - Use \`qik_get_glossary\` to see all available types with descriptions
1197
- - Use \`qik_get_content_definition\` with a specific type to see its fields`,
245
+ **Troubleshooting:**
246
+ - Check your internet connection
247
+ - Verify your access token is valid
248
+ - Try the request again in a few moments`,
1198
249
  }],
1199
250
  isError: true,
1200
251
  };
1201
252
  }
1202
- /**
1203
- * Categorizes content types for better organization in help text
1204
- */
1205
- categorizeContentTypes(types) {
1206
- const categories = {
1207
- 'Core Content': [],
1208
- 'People & Profiles': [],
1209
- 'Workflows & Processes': [],
1210
- 'Communication': [],
1211
- 'Media & Files': [],
1212
- 'System & Admin': [],
1213
- 'Other': []
1214
- };
1215
- for (const type of types) {
1216
- const key = type.key.toLowerCase();
1217
- const title = type.title.toLowerCase();
1218
- if (key.includes('profile') || key.includes('person') || key.includes('user')) {
1219
- categories['People & Profiles'].push(type);
1220
- }
1221
- else if (key.includes('workflow') || key.includes('definition') || key.includes('process')) {
1222
- categories['Workflows & Processes'].push(type);
1223
- }
1224
- else if (key.includes('comment') || key.includes('message') || key.includes('notification') || key.includes('email')) {
1225
- categories['Communication'].push(type);
1226
- }
1227
- else if (key.includes('file') || key.includes('image') || key.includes('video') || key.includes('audio')) {
1228
- categories['Media & Files'].push(type);
1229
- }
1230
- else if (key.includes('scope') || key.includes('role') || key.includes('policy') || key.includes('variable')) {
1231
- categories['System & Admin'].push(type);
1232
- }
1233
- else if (['article', 'event', 'object'].includes(key)) {
1234
- categories['Core Content'].push(type);
1235
- }
1236
- else {
1237
- categories['Other'].push(type);
1238
- }
1239
- }
1240
- let result = '';
1241
- for (const [category, categoryTypes] of Object.entries(categories)) {
1242
- if (categoryTypes.length > 0) {
1243
- result += `\n**${category}:**\n`;
1244
- result += categoryTypes.map(t => `- ${t.key}: ${t.title} (${t.plural})`).join('\n');
1245
- result += '\n';
1246
- }
1247
- }
1248
- return result;
1249
- }
1250
- /**
1251
- * Handles cases where multiple content types match
1252
- */
1253
- async handleMultipleContentTypeMatches(description, contentTypeMatches) {
1254
- const matchDetails = contentTypeMatches.map(key => {
1255
- const type = this.glossary[key];
1256
- let title = 'Unknown';
1257
- let plural = 'Unknown';
1258
- let baseType = '';
1259
- if (type && typeof type === 'object') {
1260
- if (type.definition) {
1261
- title = type.definition.title || title;
1262
- plural = type.definition.plural || plural;
1263
- baseType = type.definition.definesType || '';
1264
- }
1265
- else if (type.type) {
1266
- title = type.type.title || title;
1267
- plural = type.type.plural || plural;
1268
- }
1269
- else if (type.title) {
1270
- title = type.title || title;
1271
- plural = type.plural || plural;
1272
- baseType = type.definesType || '';
1273
- }
1274
- }
1275
- return { key, title, plural, baseType };
1276
- });
1277
- return {
1278
- content: [{
1279
- type: 'text',
1280
- text: `🔍 **MULTIPLE CONTENT TYPES FOUND**
1281
-
1282
- I found multiple content types that match "${description}". Please clarify which one you'd like to create:
1283
-
1284
- ${matchDetails.map(t => {
1285
- let description = `- **${t.key}**: ${t.title} (${t.plural})`;
1286
- if (t.baseType) {
1287
- description += ` - extends ${t.baseType}`;
1288
- }
1289
- return description;
1290
- }).join('\n')}
1291
-
1292
- **TO PROCEED:**
1293
- 1. Choose the exact content type key from above
1294
- 2. Use \`qik_create_content\` with your chosen type
1295
- 3. Or use \`qik_get_content_definition\` to see field details first
1296
-
1297
- **NEED MORE INFO?**
1298
- Use \`qik_get_content_definition\` with any of the type keys above to see their specific fields and requirements.`,
1299
- }],
1300
- isError: true,
1301
- };
1302
- }
1303
- /**
1304
- * Handles single content type match with comprehensive guidance
1305
- */
1306
- async handleSingleContentTypeMatch(contentType, description, additionalData) {
1307
- const typeInfo = this.getContentTypeInfo(contentType);
1308
- if (!typeInfo) {
1309
- return {
1310
- content: [{
1311
- type: 'text',
1312
- text: `❌ Found content type "${contentType}" but couldn't load its definition.`,
1313
- }],
1314
- isError: true,
1315
- };
1316
- }
1317
- // Extract comprehensive type information
1318
- const typeAnalysis = this.analyzeContentTypeStructure(typeInfo);
1319
- let guidance = `✅ **CONTENT TYPE FOUND: ${contentType.toUpperCase()}**\n\n`;
1320
- guidance += `**Type**: ${typeAnalysis.title}\n`;
1321
- guidance += `**Description**: ${typeAnalysis.description || 'No description available'}\n`;
1322
- if (typeAnalysis.baseType) {
1323
- guidance += `**Extends**: ${typeAnalysis.baseType}\n`;
1324
- }
1325
- guidance += `\n**FIELD STRUCTURE:**\n`;
1326
- if (typeAnalysis.requiredFields.length > 0) {
1327
- guidance += `\n**Required Fields:**\n`;
1328
- guidance += typeAnalysis.requiredFields.map(f => `- **${f.key}** (${f.title}): ${f.description || 'No description'}`).join('\n');
1329
- }
1330
- if (typeAnalysis.optionalFields.length > 0) {
1331
- guidance += `\n\n**Optional Fields:**\n`;
1332
- guidance += typeAnalysis.optionalFields.map(f => `- **${f.key}** (${f.title}): ${f.description || 'No description'}`).join('\n');
1333
- }
1334
- // Handle creation if data provided
1335
- if (additionalData && typeof additionalData === 'object' && additionalData.title) {
1336
- return await this.handleContentCreationWithData(contentType, additionalData, typeAnalysis);
1337
- }
1338
- // Provide creation guidance
1339
- guidance += await this.generateCreationGuidance(contentType, typeAnalysis);
1340
- return {
1341
- content: [{
1342
- type: 'text',
1343
- text: guidance,
1344
- }],
1345
- };
1346
- }
1347
- /**
1348
- * Analyzes content type structure for comprehensive information
1349
- */
1350
- analyzeContentTypeStructure(typeInfo) {
1351
- let fields = [];
1352
- let title = 'Unknown';
1353
- let description = '';
1354
- let baseType = '';
1355
- if (typeInfo.definition) {
1356
- fields = typeInfo.definition.fields || [];
1357
- title = typeInfo.definition.title || title;
1358
- description = typeInfo.definition.description || '';
1359
- baseType = typeInfo.definition.definesType || '';
1360
- }
1361
- else if (typeInfo.type) {
1362
- fields = typeInfo.type.fields || [];
1363
- title = typeInfo.type.title || title;
1364
- description = typeInfo.type.description || '';
1365
- }
1366
- else if (typeInfo.fields) {
1367
- fields = typeInfo.fields || [];
1368
- title = typeInfo.title || title;
1369
- description = typeInfo.description || '';
1370
- baseType = typeInfo.definesType || '';
1371
- }
1372
- const requiredFields = fields.filter((f) => f.minimum && f.minimum > 0);
1373
- const optionalFields = fields.filter((f) => !f.minimum || f.minimum === 0);
1374
- return {
1375
- title,
1376
- description,
1377
- baseType,
1378
- fields,
1379
- requiredFields,
1380
- optionalFields,
1381
- fieldCount: fields.length
1382
- };
1383
- }
1384
- /**
1385
- * Handles content creation when data is provided
1386
- */
1387
- async handleContentCreationWithData(contentType, additionalData, typeAnalysis) {
1388
- // Check if scopes are provided
1389
- if (!additionalData.meta || !additionalData.meta.scopes || !Array.isArray(additionalData.meta.scopes) || additionalData.meta.scopes.length === 0) {
1390
- const scopeTree = await this.getAvailableScopes();
1391
- if (scopeTree) {
1392
- const availableScopes = this.extractScopesWithPermissions(scopeTree, 'create');
1393
- if (availableScopes.length === 0) {
1394
- return {
1395
- content: [{
1396
- type: 'text',
1397
- text: `🚫 **PERMISSION DENIED**\n\nYou don't have permission to create content in any scopes. Please contact your administrator.`,
1398
- }],
1399
- isError: true,
1400
- };
1401
- }
1402
- return {
1403
- content: [{
1404
- type: 'text',
1405
- text: `📍 **SCOPE SELECTION REQUIRED**\n\nTo create "${additionalData.title}" as a ${typeAnalysis.title}, you need to specify which scope to create it in.\n\n**Available Scopes:**\n${availableScopes.map(s => `- **${s.id}**: ${s.path}`).join('\n')}\n\n**TO CREATE:**\nUse \`qik_create_content\` with:\n- type: "${contentType}"\n- title: "${additionalData.title}"\n- meta: { "scopes": ["scope_id_here"] }\n- data: { /* your field values */ }`,
1406
- }],
1407
- };
1408
- }
1409
- }
1410
- // Proceed with creation
1411
- const title = additionalData.title || `New ${typeAnalysis.title}`;
1412
- const data = additionalData.data || {};
1413
- const meta = additionalData.meta || {};
1414
- return await this.createContent({
1415
- type: contentType,
1416
- title,
1417
- data,
1418
- meta,
1419
- });
1420
- }
1421
- /**
1422
- * Generates comprehensive creation guidance
1423
- */
1424
- async generateCreationGuidance(contentType, typeAnalysis) {
1425
- let guidance = `\n\n**CREATION GUIDANCE:**\n`;
1426
- // Get available scopes
1427
- const scopeTree = await this.getAvailableScopes();
1428
- let scopeGuidance = '';
1429
- if (scopeTree) {
1430
- const availableScopes = this.extractScopesWithPermissions(scopeTree, 'create');
1431
- if (availableScopes.length > 0) {
1432
- scopeGuidance = `\n**Available Scopes:**\n${availableScopes.map(s => `- **${s.id}**: ${s.path}`).join('\n')}\n`;
1433
- }
1434
- }
1435
- guidance += `\n**TO CREATE THIS CONTENT:**\n`;
1436
- guidance += `Use \`qik_create_content\` with:\n`;
1437
- guidance += `- **type**: "${contentType}"\n`;
1438
- guidance += `- **title**: "Your title here"\n`;
1439
- guidance += `- **meta**: { "scopes": ["scope_id_here"] } *(required)*\n`;
1440
- guidance += `- **data**: { /* field values based on structure above */ }\n`;
1441
- guidance += scopeGuidance;
1442
- // Add specific guidance for common types
1443
- if (contentType === 'definition') {
1444
- guidance += `\n**WORKFLOW DEFINITION EXAMPLE:**\n`;
1445
- guidance += `For workflow definitions, include the workflow structure in the data field with columns, steps, and automation rules.\n`;
1446
- }
1447
- if (contentType.includes('comment') || contentType.includes('Comment')) {
1448
- guidance += `\n**COMMENT CREATION:**\n`;
1449
- guidance += `Comments require a reference to the item being commented on. Include:\n`;
1450
- guidance += `- **reference**: ID of the item to comment on\n`;
1451
- guidance += `- **referenceType**: Type of the referenced item\n`;
1452
- }
1453
- return guidance;
1454
- }
1455
- generateToolDescription(baseDescription, emoji = '') {
1456
- const prefix = emoji ? `${emoji} ` : '';
1457
- return `${prefix}${baseDescription.replace(/Qik/g, this.serverName)}`;
1458
- }
1459
253
  setupToolHandlers() {
1460
254
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
1461
- // Ensure glossary is loaded
1462
- await this.loadGlossary();
1463
255
  const tools = [
1464
- // Authentication & Session
256
+ // Core requirement 1: Authentication
1465
257
  {
1466
258
  name: 'qik_get_user_session',
1467
- description: this.generateToolDescription('Get current user session information', '👤'),
259
+ description: '👤 Get current user session information',
1468
260
  inputSchema: {
1469
261
  type: 'object',
1470
262
  properties: {},
1471
263
  },
1472
264
  },
1473
- // Content Type Discovery
265
+ // Core requirement 2: Glossary discovery
1474
266
  {
1475
267
  name: 'qik_get_glossary',
1476
- description: this.generateToolDescription('Get all available content types and their definitions', '📚'),
268
+ description: '📚 Get all available content types and their definitions',
1477
269
  inputSchema: {
1478
270
  type: 'object',
1479
271
  properties: {},
@@ -1481,34 +273,37 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1481
273
  },
1482
274
  {
1483
275
  name: 'qik_get_content_definition',
1484
- description: this.generateToolDescription('Get definition for a specific content type', '🔍'),
276
+ description: '🔍 Get definition for a specific content type',
1485
277
  inputSchema: {
1486
278
  type: 'object',
1487
279
  properties: {
1488
280
  type: {
1489
281
  type: 'string',
1490
- description: 'Content type key (e.g., "article", "profile", "event")',
282
+ description: 'Content type key',
1491
283
  enum: Object.keys(this.glossary),
1492
284
  },
1493
285
  },
1494
286
  required: ['type'],
1495
287
  },
1496
288
  },
1497
- // Content Management
289
+ // Core requirement 3: Session management (covered by user session)
290
+ {
291
+ name: 'qik_get_scopes',
292
+ description: '🔐 Get available scopes/permissions tree',
293
+ inputSchema: {
294
+ type: 'object',
295
+ properties: {},
296
+ },
297
+ },
298
+ // Core requirement 4: Basic CRUD tools
1498
299
  {
1499
300
  name: 'qik_get_content',
1500
- description: this.generateToolDescription('Get content item by ID or slug', '📄'),
301
+ description: '📄 Get content item by ID or slug',
1501
302
  inputSchema: {
1502
303
  type: 'object',
1503
304
  properties: {
1504
- id: {
1505
- type: 'string',
1506
- description: 'Content ID',
1507
- },
1508
- slug: {
1509
- type: 'string',
1510
- description: 'Content slug (e.g., "article:my-post" or "car:pathfinder")',
1511
- },
305
+ id: { type: 'string', description: 'Content ID' },
306
+ slug: { type: 'string', description: 'Content slug' },
1512
307
  },
1513
308
  oneOf: [
1514
309
  { required: ['id'] },
@@ -1518,27 +313,7 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1518
313
  },
1519
314
  {
1520
315
  name: 'qik_list_content',
1521
- description: this.generateToolDescription(`List content items with advanced filtering and search capabilities. Supports complex queries like birthdays, date ranges, and sophisticated business logic.
1522
-
1523
- **ENHANCED FILTER CAPABILITIES:**
1524
- - 40+ comparators for dates, strings, numbers, and arrays
1525
- - Hierarchical filters with 'and', 'or', 'nor' operators
1526
- - Anniversary and birthday queries (anniversarynext, anniversarypast)
1527
- - Date range filtering (datebetween, datepast, datenext)
1528
- - String matching (contains, startswith, endswith, equal)
1529
- - Numeric comparisons (greater, lesser, between)
1530
- - Array operations (in, notin, valuesgreater)
1531
-
1532
- **COMMON USE CASES:**
1533
- - Find birthdays in next 10 days: {"operator":"and","filters":[{"key":"dob","comparator":"anniversarynext","value":10,"value2":"days"}]}
1534
- - Recent content: {"operator":"and","filters":[{"key":"meta.created","comparator":"datepast","value":30,"value2":"days"}]}
1535
- - Gender filtering: {"operator":"and","filters":[{"key":"gender","comparator":"equal","value":"male"}]}
1536
- - Complex queries with OR logic for multiple conditions
1537
-
1538
- **FIELD TARGETING:**
1539
- - Use dot notation for nested fields: "meta.created", "data.customField"
1540
- - Target specific profile fields: "firstName", "lastName", "emails"
1541
- - Filter by metadata: "meta.scopes", "meta.tags", "meta.security"`, '📋'),
316
+ description: '📋 List content items with filtering and search',
1542
317
  inputSchema: {
1543
318
  type: 'object',
1544
319
  properties: {
@@ -1547,121 +322,43 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1547
322
  description: 'Content type to list',
1548
323
  enum: Object.keys(this.glossary),
1549
324
  },
1550
- search: {
1551
- type: 'string',
1552
- description: 'Search keywords - searches within title, tags, and text areas',
1553
- },
1554
- filter: this.generateEnhancedFilterSchema(),
1555
- sort: {
1556
- type: 'object',
1557
- description: 'Sorting configuration for results',
1558
- properties: {
1559
- key: {
1560
- type: 'string',
1561
- description: 'Field to sort by (e.g., "title", "meta.created", "data.customField")'
1562
- },
1563
- direction: {
1564
- type: 'string',
1565
- enum: ['asc', 'desc'],
1566
- description: 'Sort direction: ascending or descending'
1567
- },
1568
- type: {
1569
- type: 'string',
1570
- enum: ['string', 'number', 'date'],
1571
- description: 'Data type for proper sorting behavior'
1572
- },
1573
- },
1574
- },
325
+ search: { type: 'string', description: 'Search keywords' },
326
+ filter: { type: 'object', description: 'Filter criteria' },
1575
327
  page: {
1576
328
  type: 'object',
1577
- description: 'Pagination settings',
1578
329
  properties: {
1579
- size: {
1580
- type: 'number',
1581
- minimum: 1,
1582
- maximum: 100,
1583
- description: 'Number of items per page (1-100)'
1584
- },
1585
- index: {
1586
- type: 'number',
1587
- minimum: 1,
1588
- description: 'Page number to retrieve (starts at 1)'
1589
- },
330
+ size: { type: 'number', minimum: 1, maximum: 100 },
331
+ index: { type: 'number', minimum: 1 },
1590
332
  },
1591
333
  },
1592
- select: {
1593
- type: 'array',
1594
- items: { type: 'string' },
1595
- description: 'Specific fields to include in response (e.g., ["title", "data.make", "meta.created"])',
1596
- },
1597
334
  },
1598
335
  required: ['type'],
1599
336
  },
1600
337
  },
1601
338
  {
1602
339
  name: 'qik_create_content',
1603
- description: this.generateToolDescription(`Create new content item with intelligent field structure handling.
1604
-
1605
- **FIELD STRUCTURE INTELLIGENCE:**
1606
- - Automatically separates root-level fields from data object fields
1607
- - Handles comment inheritance (comments inherit scopes from referenced items)
1608
- - Validates field requirements based on content type definitions
1609
- - Supports workflow definitions, workflow cards, and all content types
1610
-
1611
- **FIELD PLACEMENT RULES:**
1612
- - **Root Level**: reference, referenceType, body, organisation, title, meta
1613
- - **Data Object**: Custom fields defined in content type definitions (definedFields)
1614
- - **Meta Object**: scopes (required), tags, security, personaAuthor, etc.
1615
-
1616
- **CONTENT TYPE EXAMPLES:**
1617
- - **Comments**: Require reference + referenceType, inherit scopes automatically
1618
- - **Workflow Definitions**: Use data object for workflow structure (columns, steps, automation)
1619
- - **Workflow Cards**: Reference profiles, link to workflow definitions
1620
- - **Profiles**: firstName, lastName at root, custom fields in data object
1621
- - **Articles**: body at root level, custom article fields in data object
1622
-
1623
- **SCOPE INHERITANCE:**
1624
- - Comments automatically inherit scopes from referenced items
1625
- - Other content types require explicit scope assignment
1626
- - Use qik_get_scopes to find available scopes with permissions
1627
-
1628
- **VALIDATION:**
1629
- - Checks content type exists and user has access
1630
- - Validates required fields based on content type definition
1631
- - Ensures proper field placement (root vs data object)`, '✨'),
340
+ description: '✨ Create new content item',
1632
341
  inputSchema: {
1633
342
  type: 'object',
1634
343
  properties: {
1635
344
  type: {
1636
345
  type: 'string',
1637
- description: 'Content type to create (use qik_get_glossary to see all available types)',
346
+ description: 'Content type to create',
1638
347
  enum: Object.keys(this.glossary),
1639
348
  },
1640
- title: {
1641
- type: 'string',
1642
- description: 'Content title (required for all content types)',
1643
- },
1644
- // Generate dynamic properties based on content types
1645
- ...this.generateDynamicContentProperties(),
349
+ title: { type: 'string', description: 'Content title' },
350
+ data: { type: 'object', description: 'Content data fields' },
1646
351
  meta: {
1647
352
  type: 'object',
1648
- description: 'Meta information (scopes required for most content types)',
353
+ description: 'Meta information (scopes required)',
1649
354
  properties: {
1650
355
  scopes: {
1651
356
  type: 'array',
1652
357
  items: { type: 'string' },
1653
- description: 'Scope IDs where this content should be stored (REQUIRED - use qik_get_scopes to find available)',
1654
- },
1655
- tags: {
1656
- type: 'array',
1657
- items: { type: 'string' },
1658
- description: 'Tag IDs for categorization and search',
1659
- },
1660
- security: {
1661
- type: 'string',
1662
- enum: ['public', 'secure', 'private'],
1663
- description: 'Security level: public (everyone), secure (authenticated), private (restricted)',
358
+ description: 'Scope IDs where content should be created',
1664
359
  },
360
+ tags: { type: 'array', items: { type: 'string' } },
361
+ security: { type: 'string', enum: ['public', 'secure', 'private'] },
1665
362
  },
1666
363
  },
1667
364
  },
@@ -1670,341 +367,32 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1670
367
  },
1671
368
  {
1672
369
  name: 'qik_update_content',
1673
- description: this.generateToolDescription('Update existing content item', '✏️'),
370
+ description: '✏️ Update existing content item',
1674
371
  inputSchema: {
1675
372
  type: 'object',
1676
373
  properties: {
1677
- id: {
1678
- type: 'string',
1679
- description: 'Content ID to update',
1680
- },
1681
- data: {
1682
- type: 'object',
1683
- description: 'Data to update (partial update)',
1684
- },
1685
- replace: {
1686
- type: 'boolean',
1687
- description: 'Whether to replace entire content (PUT) or merge (PATCH)',
1688
- default: false,
1689
- },
374
+ id: { type: 'string', description: 'Content ID to update' },
375
+ data: { type: 'object', description: 'Data to update' },
1690
376
  },
1691
377
  required: ['id', 'data'],
1692
378
  },
1693
379
  },
1694
380
  {
1695
381
  name: 'qik_delete_content',
1696
- description: this.generateToolDescription('Delete content item', '🗑️'),
1697
- inputSchema: {
1698
- type: 'object',
1699
- properties: {
1700
- id: {
1701
- type: 'string',
1702
- description: 'Content ID to delete',
1703
- },
1704
- },
1705
- required: ['id'],
1706
- },
1707
- },
1708
- // Profile Management
1709
- {
1710
- name: 'qik_list_profiles',
1711
- description: this.generateToolDescription('Search and list profiles/people', '👥'),
1712
- inputSchema: {
1713
- type: 'object',
1714
- properties: {
1715
- search: {
1716
- type: 'string',
1717
- description: 'Search by name or email',
1718
- },
1719
- filter: {
1720
- type: 'object',
1721
- description: 'Filter criteria',
1722
- },
1723
- page: {
1724
- type: 'object',
1725
- properties: {
1726
- size: { type: 'number', minimum: 1, maximum: 100 },
1727
- index: { type: 'number', minimum: 1 },
1728
- },
1729
- },
1730
- },
1731
- },
1732
- },
1733
- {
1734
- name: 'qik_create_profile',
1735
- description: this.generateToolDescription('Create new profile/person', '👤'),
1736
- inputSchema: {
1737
- type: 'object',
1738
- properties: {
1739
- firstName: {
1740
- type: 'string',
1741
- description: 'First name',
1742
- },
1743
- lastName: {
1744
- type: 'string',
1745
- description: 'Last name',
1746
- },
1747
- emails: {
1748
- type: 'array',
1749
- items: { type: 'string' },
1750
- description: 'Email addresses',
1751
- },
1752
- phoneNumbers: {
1753
- type: 'array',
1754
- items: {
1755
- type: 'object',
1756
- properties: {
1757
- label: { type: 'string' },
1758
- countryCode: { type: 'string' },
1759
- number: { type: 'string' },
1760
- },
1761
- },
1762
- description: 'Phone numbers',
1763
- },
1764
- data: {
1765
- type: 'object',
1766
- description: 'Additional profile data',
1767
- },
1768
- meta: {
1769
- type: 'object',
1770
- description: 'Meta information (scopes, tags, etc.)',
1771
- },
1772
- },
1773
- required: ['firstName', 'lastName'],
1774
- },
1775
- },
1776
- {
1777
- name: 'qik_get_profile_timeline',
1778
- 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', '📅'),
1779
- inputSchema: {
1780
- type: 'object',
1781
- properties: {
1782
- id: {
1783
- type: 'string',
1784
- description: 'Profile ID to get timeline for',
1785
- },
1786
- },
1787
- required: ['id'],
1788
- },
1789
- },
1790
- {
1791
- name: 'qik_get_profile_info',
1792
- 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', '🤔'),
1793
- inputSchema: {
1794
- type: 'object',
1795
- properties: {
1796
- query: {
1797
- type: 'string',
1798
- description: 'Natural language query about a person (e.g., "Tell me about Jeff", "What has John been up to?", "Get Sarah\'s contact info")',
1799
- },
1800
- profileId: {
1801
- type: 'string',
1802
- description: 'Profile ID if known (optional - can search by name if not provided)',
1803
- },
1804
- profileName: {
1805
- type: 'string',
1806
- description: 'Person\'s name to search for if ID not provided (optional)',
1807
- },
1808
- includeTimeline: {
1809
- type: 'boolean',
1810
- description: 'Whether to include timeline/activity data (optional - will be determined from query if not specified)',
1811
- },
1812
- },
1813
- required: ['query'],
1814
- },
1815
- },
1816
- // Form Management
1817
- {
1818
- name: 'qik_get_form',
1819
- description: this.generateToolDescription('Get form definition', '📝'),
1820
- inputSchema: {
1821
- type: 'object',
1822
- properties: {
1823
- id: {
1824
- type: 'string',
1825
- description: 'Form ID',
1826
- },
1827
- },
1828
- required: ['id'],
1829
- },
1830
- },
1831
- {
1832
- name: 'qik_submit_form',
1833
- description: this.generateToolDescription('Submit form data', '📤'),
1834
- inputSchema: {
1835
- type: 'object',
1836
- properties: {
1837
- id: {
1838
- type: 'string',
1839
- description: 'Form ID',
1840
- },
1841
- data: {
1842
- type: 'object',
1843
- description: 'Form submission data',
1844
- },
1845
- },
1846
- required: ['id', 'data'],
1847
- },
1848
- },
1849
- // File Management
1850
- {
1851
- name: 'qik_upload_file',
1852
- description: this.generateToolDescription('Upload file to platform', '📁'),
1853
- inputSchema: {
1854
- type: 'object',
1855
- properties: {
1856
- title: {
1857
- type: 'string',
1858
- description: 'File title',
1859
- },
1860
- fileData: {
1861
- type: 'string',
1862
- description: 'Base64 encoded file data',
1863
- },
1864
- fileName: {
1865
- type: 'string',
1866
- description: 'Original file name',
1867
- },
1868
- mimeType: {
1869
- type: 'string',
1870
- description: 'File MIME type',
1871
- },
1872
- meta: {
1873
- type: 'object',
1874
- description: 'Meta information (scopes, tags, etc.)',
1875
- },
1876
- },
1877
- required: ['title', 'fileData', 'fileName'],
1878
- },
1879
- },
1880
- // Search & Discovery
1881
- {
1882
- name: 'qik_search_content',
1883
- description: this.generateToolDescription('Global content search across all types', '🔎'),
1884
- inputSchema: {
1885
- type: 'object',
1886
- properties: {
1887
- query: {
1888
- type: 'string',
1889
- description: 'Search query',
1890
- },
1891
- types: {
1892
- type: 'array',
1893
- items: {
1894
- type: 'string',
1895
- enum: Object.keys(this.glossary),
1896
- },
1897
- description: 'Content types to search in',
1898
- },
1899
- limit: {
1900
- type: 'number',
1901
- minimum: 1,
1902
- maximum: 100,
1903
- default: 20,
1904
- },
1905
- },
1906
- required: ['query'],
1907
- },
1908
- },
1909
- {
1910
- name: 'qik_get_scopes',
1911
- description: this.generateToolDescription('Get available scopes/permissions tree', '🔐'),
1912
- inputSchema: {
1913
- type: 'object',
1914
- properties: {},
1915
- },
1916
- },
1917
- // Utility Tools
1918
- {
1919
- name: 'qik_get_smartlist',
1920
- description: this.generateToolDescription('Execute a smartlist query', '📊'),
382
+ description: '🗑️ Delete content item',
1921
383
  inputSchema: {
1922
384
  type: 'object',
1923
385
  properties: {
1924
- id: {
1925
- type: 'string',
1926
- description: 'Smartlist ID',
1927
- },
386
+ id: { type: 'string', description: 'Content ID to delete' },
1928
387
  },
1929
388
  required: ['id'],
1930
389
  },
1931
390
  },
1932
- // Intelligent Content Creation with Advanced Disambiguation
1933
- {
1934
- name: 'qik_create_content_intelligent',
1935
- description: this.generateToolDescription(`Intelligently create content with advanced disambiguation logic.
1936
-
1937
- **WORKFLOW DISAMBIGUATION:**
1938
- - "create a workflow" → Creates workflow DEFINITION (template)
1939
- - "add Jim to workflow X" → Creates workflow CARD (instance)
1940
- - "design student onboarding workflow" → Creates workflow DEFINITION
1941
- - "assign Sarah to project workflow" → Creates workflow CARD
1942
-
1943
- **CONTENT TYPE DISAMBIGUATION:**
1944
- - Automatically detects intent between definitions vs instances
1945
- - Provides comprehensive guidance for workflow systems
1946
- - Categorizes content types for better organization
1947
- - Handles scope permissions and requirements
1948
-
1949
- **EXAMPLES:**
1950
- - "create an incident report" → Finds incident report content type
1951
- - "make a new workflow" → Guides through workflow definition creation
1952
- - "add person to existing workflow" → Guides through workflow card creation
1953
- - "design a student induction process" → Creates workflow definition
1954
-
1955
- **WORKFLOW CONCEPTS EXPLAINED:**
1956
- - **Workflow Definition**: Template with columns, steps, automation rules
1957
- - **Workflow Card**: Individual items that move through the workflow
1958
- - **Columns**: Stages like "To Do", "In Progress", "Done"
1959
- - **Steps**: Specific positions within columns
1960
- - **Automation**: Entry/exit/success/fail functions`, '🧠'),
1961
- inputSchema: {
1962
- type: 'object',
1963
- properties: {
1964
- description: {
1965
- type: 'string',
1966
- description: 'Natural language description of what to create. Examples: "create an incident report", "make a new workflow", "add Jim to student workflow", "design onboarding process"',
1967
- },
1968
- title: {
1969
- type: 'string',
1970
- description: 'Title for the content (optional - will be requested if needed)',
1971
- },
1972
- data: {
1973
- type: 'object',
1974
- description: 'Content data fields (optional - will be guided through structure)',
1975
- },
1976
- meta: {
1977
- type: 'object',
1978
- description: 'Meta information including scopes, tags, security level (will be guided through requirements)',
1979
- properties: {
1980
- scopes: {
1981
- type: 'array',
1982
- items: { type: 'string' },
1983
- description: 'Scope IDs where content should be created'
1984
- },
1985
- tags: {
1986
- type: 'array',
1987
- items: { type: 'string' },
1988
- description: 'Tag IDs for categorization'
1989
- },
1990
- security: {
1991
- type: 'string',
1992
- enum: ['public', 'secure', 'private'],
1993
- description: 'Security level for the content'
1994
- }
1995
- }
1996
- },
1997
- },
1998
- required: ['description'],
1999
- },
2000
- },
2001
391
  ];
2002
392
  return { tools };
2003
393
  });
2004
394
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
2005
395
  try {
2006
- // Ensure glossary is fresh
2007
- await this.loadGlossary();
2008
396
  switch (request.params.name) {
2009
397
  case 'qik_get_user_session':
2010
398
  return await this.getUserSession();
@@ -2012,6 +400,8 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
2012
400
  return await this.getGlossary();
2013
401
  case 'qik_get_content_definition':
2014
402
  return await this.getContentDefinition(request.params.arguments);
403
+ case 'qik_get_scopes':
404
+ return await this.getScopes();
2015
405
  case 'qik_get_content':
2016
406
  return await this.getContent(request.params.arguments);
2017
407
  case 'qik_list_content':
@@ -2022,32 +412,6 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
2022
412
  return await this.updateContent(request.params.arguments);
2023
413
  case 'qik_delete_content':
2024
414
  return await this.deleteContent(request.params.arguments);
2025
- case 'qik_list_profiles':
2026
- return await this.listProfiles(request.params.arguments);
2027
- case 'qik_create_profile':
2028
- return await this.createProfile(request.params.arguments);
2029
- case 'qik_get_form':
2030
- return await this.getForm(request.params.arguments);
2031
- case 'qik_submit_form':
2032
- return await this.submitForm(request.params.arguments);
2033
- case 'qik_upload_file':
2034
- return await this.uploadFile(request.params.arguments);
2035
- case 'qik_search_content':
2036
- return await this.searchContent(request.params.arguments);
2037
- case 'qik_get_scopes':
2038
- return await this.getScopes();
2039
- case 'qik_get_smartlist':
2040
- return await this.getSmartlist(request.params.arguments);
2041
- case 'qik_get_profile_timeline':
2042
- return await this.getProfileTimeline(request.params.arguments);
2043
- case 'qik_get_profile_info':
2044
- return await this.getProfileInfo(request.params.arguments);
2045
- case 'qik_create_content_intelligent':
2046
- return await this.intelligentContentCreation(request.params.arguments.description, {
2047
- title: request.params.arguments.title,
2048
- data: request.params.arguments.data,
2049
- meta: request.params.arguments.meta,
2050
- });
2051
415
  default:
2052
416
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
2053
417
  }
@@ -2055,34 +419,7 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
2055
419
  catch (error) {
2056
420
  this.log(`Error in ${request.params.name}: ${this.formatError(error)}`);
2057
421
  if (axios.isAxiosError(error)) {
2058
- const axiosError = error;
2059
- if (axiosError.response?.status === 401) {
2060
- return {
2061
- content: [{
2062
- type: 'text',
2063
- text: 'Authentication failed. Please check your access token configuration.',
2064
- }],
2065
- isError: true,
2066
- };
2067
- }
2068
- else if (axiosError.response?.status === 403) {
2069
- return {
2070
- content: [{
2071
- type: 'text',
2072
- text: 'Access denied. Your token may not have permission for this operation.',
2073
- }],
2074
- isError: true,
2075
- };
2076
- }
2077
- else if (axiosError.response?.status === 429) {
2078
- return {
2079
- content: [{
2080
- type: 'text',
2081
- text: 'Rate limit exceeded. Please try again later.',
2082
- }],
2083
- isError: true,
2084
- };
2085
- }
422
+ return await this.handleApiError(error, request.params.name, request.params.arguments);
2086
423
  }
2087
424
  return {
2088
425
  content: [{
@@ -2094,7 +431,7 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
2094
431
  }
2095
432
  });
2096
433
  }
2097
- // Enhanced tool implementations
434
+ // Simplified tool implementations - let API handle validation
2098
435
  async getUserSession() {
2099
436
  if (!this.userSession) {
2100
437
  await this.loadUserSession();
@@ -2107,102 +444,33 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
2107
444
  };
2108
445
  }
2109
446
  async getGlossary() {
2110
- await this.loadGlossary(true); // Force refresh
2111
- // Create a clean list of all content types with their proper titles and descriptions
2112
- const contentTypes = Object.entries(this.glossary).map(([key, contentType]) => {
2113
- let title = 'Unknown';
2114
- let plural = 'Unknown';
2115
- let description = '';
2116
- let fieldCount = 0;
2117
- let baseType = null;
2118
- let fields = [];
2119
- if (contentType && typeof contentType === 'object') {
2120
- // Handle direct properties on contentType (most common structure)
2121
- if (contentType.title) {
2122
- title = contentType.title;
2123
- plural = contentType.plural || title + 's';
2124
- description = contentType.description || '';
2125
- fields = contentType.fields || [];
2126
- baseType = contentType.definesType;
2127
- }
2128
- // Handle nested definition structure
2129
- else if (contentType.definition) {
2130
- title = contentType.definition.title || title;
2131
- plural = contentType.definition.plural || title + 's';
2132
- description = contentType.definition.description || '';
2133
- fields = contentType.definition.fields || [];
2134
- baseType = contentType.definition.definesType;
2135
- }
2136
- // Handle nested type structure
2137
- else if (contentType.type) {
2138
- title = contentType.type.title || title;
2139
- plural = contentType.type.plural || title + 's';
2140
- description = contentType.type.description || '';
2141
- fields = contentType.type.fields || [];
2142
- }
2143
- fieldCount = fields.length;
2144
- }
2145
- return {
2146
- key,
2147
- title,
2148
- plural,
2149
- description,
2150
- fieldCount,
2151
- baseType,
2152
- isExtension: baseType && baseType !== key,
2153
- fields: fields.map(f => ({
2154
- key: f.key,
2155
- title: f.title,
2156
- type: f.type,
2157
- required: this.isFieldRequired(f),
2158
- description: f.description || ''
2159
- }))
2160
- };
2161
- });
2162
- // Sort alphabetically by title for easy reading
2163
- const sortedTypes = contentTypes.sort((a, b) => a.title.localeCompare(b.title));
2164
- // Create a summary that clearly shows each type is unique
2165
- const typesList = sortedTypes.map(type => {
2166
- let typeDescription = `${type.key}: ${type.title}`;
2167
- if (type.isExtension && type.baseType) {
2168
- typeDescription += ` (extends ${type.baseType})`;
2169
- }
2170
- if (type.plural !== type.title + 's') {
2171
- typeDescription += ` - plural: ${type.plural}`;
2172
- }
2173
- if (type.fieldCount > 0) {
2174
- typeDescription += ` - ${type.fieldCount} fields`;
2175
- }
2176
- return typeDescription;
2177
- });
447
+ await this.loadGlossary();
448
+ const contentTypes = Object.entries(this.glossary).map(([key, type]) => ({
449
+ key,
450
+ title: type.title,
451
+ plural: type.plural,
452
+ description: type.description,
453
+ fieldCount: type.fields?.length || 0,
454
+ }));
2178
455
  return {
2179
456
  content: [{
2180
457
  type: 'text',
2181
458
  text: `Available Content Types (${contentTypes.length} total):
2182
459
 
2183
- ${typesList.join('\n')}
2184
-
2185
- Each content type above is unique and can be used for content creation. Extended types (those that show "extends X") are specialized versions of base types with their own specific fields and purposes.
2186
-
2187
- For detailed field information about any content type, use qik_get_content_definition with the type key.
460
+ ${contentTypes.map(t => `${t.key}: ${t.title} (${t.fieldCount} fields)`).join('\n')}
2188
461
 
2189
462
  Full glossary data:
2190
- ${JSON.stringify({
2191
- contentTypes: sortedTypes,
2192
- totalTypes: contentTypes.length,
2193
- extensionTypes: contentTypes.filter(t => t.isExtension).length,
2194
- baseTypes: contentTypes.filter(t => !t.isExtension).length
2195
- }, null, 2)}`,
463
+ ${JSON.stringify(this.glossary, null, 2)}`,
2196
464
  }],
2197
465
  };
2198
466
  }
2199
467
  async getContentDefinition(args) {
2200
- const validation = this.validateContentType(args.type);
2201
- if (!validation.valid) {
468
+ if (!this.glossary[args.type]) {
469
+ const available = Object.keys(this.glossary).join(', ');
2202
470
  return {
2203
471
  content: [{
2204
472
  type: 'text',
2205
- text: validation.error,
473
+ text: `Content type '${args.type}' not found. Available types: ${available}`,
2206
474
  }],
2207
475
  isError: true,
2208
476
  };
@@ -2217,18 +485,32 @@ ${JSON.stringify({
2217
485
  };
2218
486
  }
2219
487
  catch (error) {
488
+ if (axios.isAxiosError(error)) {
489
+ return await this.handleApiError(error, 'get content definition', args);
490
+ }
491
+ throw error;
492
+ }
493
+ }
494
+ async getScopes() {
495
+ try {
496
+ const response = await this.axiosInstance.get('/scope/tree');
2220
497
  return {
2221
498
  content: [{
2222
499
  type: 'text',
2223
- text: `Failed to fetch definition for '${args.type}': ${this.formatError(error)}`,
500
+ text: JSON.stringify(response.data, null, 2),
2224
501
  }],
2225
- isError: true,
2226
502
  };
2227
503
  }
504
+ catch (error) {
505
+ if (axios.isAxiosError(error)) {
506
+ return await this.handleApiError(error, 'get scopes', {});
507
+ }
508
+ throw error;
509
+ }
2228
510
  }
2229
511
  async getContent(args) {
2230
- let response;
2231
512
  try {
513
+ let response;
2232
514
  if (args.id) {
2233
515
  response = await this.axiosInstance.get(`/content/${args.id}`);
2234
516
  }
@@ -2246,23 +528,19 @@ ${JSON.stringify({
2246
528
  };
2247
529
  }
2248
530
  catch (error) {
2249
- const identifier = args.id || args.slug;
2250
- return {
2251
- content: [{
2252
- type: 'text',
2253
- text: `Failed to fetch content '${identifier}': ${this.formatError(error)}`,
2254
- }],
2255
- isError: true,
2256
- };
531
+ if (axios.isAxiosError(error)) {
532
+ return await this.handleApiError(error, 'get content', args);
533
+ }
534
+ throw error;
2257
535
  }
2258
536
  }
2259
537
  async listContent(args) {
2260
- const validation = this.validateContentType(args.type);
2261
- if (!validation.valid) {
538
+ if (!this.glossary[args.type]) {
539
+ const available = Object.keys(this.glossary).join(', ');
2262
540
  return {
2263
541
  content: [{
2264
542
  type: 'text',
2265
- text: validation.error,
543
+ text: `Content type '${args.type}' not found. Available types: ${available}`,
2266
544
  }],
2267
545
  isError: true,
2268
546
  };
@@ -2271,9 +549,7 @@ ${JSON.stringify({
2271
549
  const response = await this.axiosInstance.post(`/content/${args.type}/list`, {
2272
550
  search: args.search || '',
2273
551
  filter: args.filter || {},
2274
- sort: args.sort || { key: 'meta.created', direction: 'desc', type: 'date' },
2275
552
  page: args.page || { size: 20, index: 1 },
2276
- select: args.select || [],
2277
553
  });
2278
554
  return {
2279
555
  content: [{
@@ -2283,867 +559,84 @@ ${JSON.stringify({
2283
559
  };
2284
560
  }
2285
561
  catch (error) {
2286
- return {
2287
- content: [{
2288
- type: 'text',
2289
- text: `Failed to list ${args.type} content: ${this.formatError(error)}`,
2290
- }],
2291
- isError: true,
2292
- };
562
+ if (axios.isAxiosError(error)) {
563
+ return await this.handleApiError(error, 'list content', args);
564
+ }
565
+ throw error;
2293
566
  }
2294
567
  }
2295
568
  async createContent(args) {
2296
- const validation = this.validateContentType(args.type);
2297
- if (!validation.valid) {
569
+ if (!this.glossary[args.type]) {
570
+ const available = Object.keys(this.glossary).join(', ');
2298
571
  return {
2299
572
  content: [{
2300
573
  type: 'text',
2301
- text: validation.error,
574
+ text: `Content type '${args.type}' not found. Available types: ${available}`,
2302
575
  }],
2303
576
  isError: true,
2304
577
  };
2305
578
  }
2306
- // Simplified approach - send data as provided and let server handle validation
2307
579
  try {
2308
- // Create payload from args as-is
2309
- const payload = { ...args };
2310
- // Handle scope inheritance for comment types
2311
- const isCommentType = args.type === 'comment' || args.type.includes('Comment');
2312
- if (isCommentType && payload.reference && (!payload.meta?.scopes || payload.meta.scopes.length === 0)) {
2313
- try {
2314
- // Fetch the referenced item to get its scopes
2315
- const referencedItem = await this.axiosInstance.get(`/content/${payload.reference}`);
2316
- if (referencedItem.data && referencedItem.data.meta && referencedItem.data.meta.scopes) {
2317
- if (!payload.meta)
2318
- payload.meta = {};
2319
- payload.meta.scopes = referencedItem.data.meta.scopes;
2320
- this.log(`Inherited scopes from referenced item ${payload.reference}: ${payload.meta.scopes.join(', ')}`);
2321
- }
2322
- }
2323
- catch (error) {
2324
- this.log(`Failed to fetch referenced item ${payload.reference} for scope inheritance: ${this.formatError(error)}`);
2325
- // Continue without scope inheritance if we can't fetch the referenced item
2326
- }
2327
- }
2328
- const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
580
+ const response = await this.axiosInstance.post(`/content/${args.type}/create`, args);
2329
581
  return {
2330
582
  content: [{
2331
583
  type: 'text',
2332
- text: `✅ **SUCCESSFULLY CREATED ${args.type.toUpperCase()} CONTENT**
584
+ text: `✅ Successfully created ${args.type}:
2333
585
 
2334
- **Created Content:**
2335
586
  ${JSON.stringify(response.data, null, 2)}`,
2336
587
  }],
2337
588
  };
2338
589
  }
2339
590
  catch (error) {
2340
- // Pass through server errors directly - they contain the authoritative validation messages
2341
- return {
2342
- content: [{
2343
- type: 'text',
2344
- text: `Failed to create ${args.type}: ${this.formatError(error)}
2345
-
2346
- 💡 **Field Placement Guidance:**
2347
- - **Root Level**: title, reference, referenceType, body, organisation, meta
2348
- - **Data Object**: Custom fields defined in content type's "definedFields" array
2349
- - Use \`qik_get_content_definition\` to see the exact field structure for this content type`,
2350
- }],
2351
- isError: true,
2352
- };
591
+ if (axios.isAxiosError(error)) {
592
+ return await this.handleApiError(error, 'create content', args);
593
+ }
594
+ throw error;
2353
595
  }
2354
596
  }
2355
597
  async updateContent(args) {
2356
598
  try {
2357
- const method = args.replace ? 'put' : 'patch';
2358
- const response = await this.axiosInstance[method](`/content/${args.id}`, args.data);
599
+ const response = await this.axiosInstance.patch(`/content/${args.id}`, args.data);
2359
600
  return {
2360
601
  content: [{
2361
602
  type: 'text',
2362
- text: JSON.stringify(response.data, null, 2),
2363
- }],
2364
- };
2365
- }
2366
- catch (error) {
2367
- return {
2368
- content: [{
2369
- type: 'text',
2370
- text: `Failed to update content ${args.id}: ${this.formatError(error)}`,
2371
- }],
2372
- isError: true,
2373
- };
2374
- }
2375
- }
2376
- async deleteContent(args) {
2377
- try {
2378
- await this.axiosInstance.delete(`/content/${args.id}`);
2379
- return {
2380
- content: [{
2381
- type: 'text',
2382
- text: `Content ${args.id} deleted successfully`,
2383
- }],
2384
- };
2385
- }
2386
- catch (error) {
2387
- return {
2388
- content: [{
2389
- type: 'text',
2390
- text: `Failed to delete content ${args.id}: ${this.formatError(error)}`,
2391
- }],
2392
- isError: true,
2393
- };
2394
- }
2395
- }
2396
- async listProfiles(args) {
2397
- try {
2398
- const response = await this.axiosInstance.post('/content/profile/list', {
2399
- search: args.search || '',
2400
- filter: args.filter || {},
2401
- page: args.page || { size: 20, index: 1 },
2402
- });
2403
- return {
2404
- content: [{
2405
- type: 'text',
2406
- text: JSON.stringify(response.data, null, 2),
2407
- }],
2408
- };
2409
- }
2410
- catch (error) {
2411
- return {
2412
- content: [{
2413
- type: 'text',
2414
- text: `Failed to list profiles: ${this.formatError(error)}`,
2415
- }],
2416
- isError: true,
2417
- };
2418
- }
2419
- }
2420
- async createProfile(args) {
2421
- const payload = {
2422
- firstName: args.firstName,
2423
- lastName: args.lastName,
2424
- emails: args.emails || [],
2425
- phoneNumbers: args.phoneNumbers || [],
2426
- data: args.data || {},
2427
- meta: args.meta || {},
2428
- };
2429
- try {
2430
- const response = await this.axiosInstance.post('/content/profile/create', payload);
2431
- return {
2432
- content: [{
2433
- type: 'text',
2434
- text: JSON.stringify(response.data, null, 2),
2435
- }],
2436
- };
2437
- }
2438
- catch (error) {
2439
- return {
2440
- content: [{
2441
- type: 'text',
2442
- text: `Failed to create profile: ${this.formatError(error)}`,
2443
- }],
2444
- isError: true,
2445
- };
2446
- }
2447
- }
2448
- async getProfileTimeline(args) {
2449
- try {
2450
- const response = await this.axiosInstance.get(`/profile/${args.id}/timeline`);
2451
- return {
2452
- content: [{
2453
- type: 'text',
2454
- text: `📅 **PROFILE TIMELINE FOR ${args.id}**
2455
-
2456
- This timeline provides chronological activity data including recent actions, content created, events attended, workflow activities, and other activity logs.
603
+ text: `✅ Successfully updated content:
2457
604
 
2458
- **Timeline Data:**
2459
605
  ${JSON.stringify(response.data, null, 2)}`,
2460
606
  }],
2461
607
  };
2462
608
  }
2463
609
  catch (error) {
2464
- return {
2465
- content: [{
2466
- type: 'text',
2467
- text: `Failed to get profile timeline for ${args.id}: ${this.formatError(error)}`,
2468
- }],
2469
- isError: true,
2470
- };
2471
- }
2472
- }
2473
- async getProfileInfo(args) {
2474
- const normalizedQuery = args.query.toLowerCase().trim();
2475
- // Analyze the query to determine intent
2476
- const timelineKeywords = [
2477
- 'activity', 'timeline', 'been up to', 'doing', 'recent', 'lately',
2478
- 'actions', 'history', 'events', 'workflow', 'progress'
2479
- ];
2480
- const basicInfoKeywords = [
2481
- 'contact', 'email', 'phone', 'details', 'information', 'about'
2482
- ];
2483
- const wantsTimeline = args.includeTimeline !== undefined
2484
- ? args.includeTimeline
2485
- : timelineKeywords.some(keyword => normalizedQuery.includes(keyword));
2486
- const wantsBasicInfo = basicInfoKeywords.some(keyword => normalizedQuery.includes(keyword));
2487
- // If the intent is ambiguous, ask for clarification
2488
- if (!wantsTimeline && !wantsBasicInfo && args.includeTimeline === undefined) {
2489
- const personName = args.profileName || (args.profileId ? `person (ID: ${args.profileId})` : 'this person');
2490
- return {
2491
- content: [{
2492
- type: 'text',
2493
- text: `🤔 **CLARIFICATION NEEDED**
2494
-
2495
- You asked: "${args.query}"
2496
-
2497
- I can provide different types of information about ${personName}:
2498
-
2499
- **OPTION 1: Basic Profile Details** 📋
2500
- - Contact information (email, phone)
2501
- - Basic demographic data
2502
- - Profile fields and custom data
2503
- → Use: \`qik_get_content\` or \`qik_list_profiles\`
2504
-
2505
- **OPTION 2: Activity Timeline** 📅
2506
- - Recent actions and activities
2507
- - Content they've created or been involved with
2508
- - Workflow progress and events attended
2509
- - Much richer, more colorful activity data
2510
- → Use: \`qik_get_profile_timeline\`
2511
-
2512
- **Which would be more helpful for your needs?**
2513
-
2514
- To get both, you can:
2515
- 1. First get basic profile info with \`qik_list_profiles\` (search by name)
2516
- 2. Then get timeline data with \`qik_get_profile_timeline\` (using the profile ID)`,
2517
- }],
2518
- };
2519
- }
2520
- let profileId = args.profileId;
2521
- // If no profile ID provided, try to find the profile by name
2522
- if (!profileId && args.profileName) {
2523
- try {
2524
- const searchResponse = await this.axiosInstance.post('/content/profile/list', {
2525
- search: args.profileName,
2526
- page: { size: 5, index: 1 },
2527
- });
2528
- if (searchResponse.data.items && searchResponse.data.items.length > 0) {
2529
- if (searchResponse.data.items.length === 1) {
2530
- profileId = searchResponse.data.items[0]._id;
2531
- }
2532
- else {
2533
- // Multiple matches found
2534
- const matches = searchResponse.data.items.map((p) => `- **${p._id}**: ${p.firstName} ${p.lastName} (${p.emails?.[0] || 'no email'})`).join('\n');
2535
- return {
2536
- content: [{
2537
- type: 'text',
2538
- text: `🔍 **MULTIPLE PROFILES FOUND**
2539
-
2540
- Found ${searchResponse.data.items.length} profiles matching "${args.profileName}":
2541
-
2542
- ${matches}
2543
-
2544
- Please specify which profile you want by using the profile ID with \`qik_get_profile_timeline\` or \`qik_get_content\`.`,
2545
- }],
2546
- };
2547
- }
2548
- }
2549
- else {
2550
- return {
2551
- content: [{
2552
- type: 'text',
2553
- text: `❌ **PROFILE NOT FOUND**
2554
-
2555
- No profiles found matching "${args.profileName}".
2556
-
2557
- Try:
2558
- - Using \`qik_list_profiles\` with a broader search
2559
- - Checking the spelling of the name
2560
- - Using the exact profile ID if you have it`,
2561
- }],
2562
- isError: true,
2563
- };
2564
- }
2565
- }
2566
- catch (error) {
2567
- return {
2568
- content: [{
2569
- type: 'text',
2570
- text: `Failed to search for profile "${args.profileName}": ${this.formatError(error)}`,
2571
- }],
2572
- isError: true,
2573
- };
2574
- }
2575
- }
2576
- // Now we have a profile ID, get the appropriate information
2577
- if (wantsTimeline && profileId) {
2578
- return await this.getProfileTimeline({ id: profileId });
2579
- }
2580
- else if (profileId) {
2581
- // Get basic profile information
2582
- try {
2583
- const response = await this.axiosInstance.get(`/content/${profileId}`);
2584
- return {
2585
- content: [{
2586
- type: 'text',
2587
- text: `👤 **PROFILE INFORMATION**
2588
-
2589
- **Basic Details:**
2590
- ${JSON.stringify(response.data, null, 2)}
2591
-
2592
- 💡 **Want more activity details?** Use \`qik_get_profile_timeline\` with ID: ${profileId} to see their recent activities, workflow progress, and timeline data.`,
2593
- }],
2594
- };
2595
- }
2596
- catch (error) {
2597
- return {
2598
- content: [{
2599
- type: 'text',
2600
- text: `Failed to get profile information for ${profileId}: ${this.formatError(error)}`,
2601
- }],
2602
- isError: true,
2603
- };
610
+ if (axios.isAxiosError(error)) {
611
+ return await this.handleApiError(error, 'update content', args);
2604
612
  }
2605
- }
2606
- else {
2607
- return {
2608
- content: [{
2609
- type: 'text',
2610
- text: `❌ **MISSING PROFILE IDENTIFIER**
2611
-
2612
- To get profile information, I need either:
2613
- - **profileId**: The exact profile ID
2614
- - **profileName**: The person's name to search for
2615
-
2616
- Please provide one of these and try again.`,
2617
- }],
2618
- isError: true,
2619
- };
613
+ throw error;
2620
614
  }
2621
615
  }
2622
- async getForm(args) {
2623
- try {
2624
- const response = await this.axiosInstance.get(`/form/${args.id}`);
2625
- return {
2626
- content: [{
2627
- type: 'text',
2628
- text: JSON.stringify(response.data, null, 2),
2629
- }],
2630
- };
2631
- }
2632
- catch (error) {
2633
- return {
2634
- content: [{
2635
- type: 'text',
2636
- text: `Failed to get form ${args.id}: ${this.formatError(error)}`,
2637
- }],
2638
- isError: true,
2639
- };
2640
- }
2641
- }
2642
- async submitForm(args) {
2643
- try {
2644
- const response = await this.axiosInstance.post(`/form/${args.id}`, args.data);
2645
- return {
2646
- content: [{
2647
- type: 'text',
2648
- text: JSON.stringify(response.data, null, 2),
2649
- }],
2650
- };
2651
- }
2652
- catch (error) {
2653
- return {
2654
- content: [{
2655
- type: 'text',
2656
- text: `Failed to submit form ${args.id}: ${this.formatError(error)}`,
2657
- }],
2658
- isError: true,
2659
- };
2660
- }
2661
- }
2662
- async uploadFile(args) {
2663
- try {
2664
- const formData = new FormData();
2665
- // Convert base64 to buffer
2666
- const fileBuffer = Buffer.from(args.fileData, 'base64');
2667
- formData.append('file', fileBuffer, args.fileName);
2668
- const jsonData = {
2669
- title: args.title,
2670
- meta: args.meta || {},
2671
- };
2672
- formData.append('json', JSON.stringify(jsonData));
2673
- const response = await this.axiosInstance.post('/file/upload', formData, {
2674
- headers: {
2675
- ...formData.getHeaders(),
2676
- 'Authorization': `Bearer ${QIK_ACCESS_TOKEN}`,
2677
- },
2678
- });
2679
- return {
2680
- content: [{
2681
- type: 'text',
2682
- text: JSON.stringify(response.data, null, 2),
2683
- }],
2684
- };
2685
- }
2686
- catch (error) {
2687
- return {
2688
- content: [{
2689
- type: 'text',
2690
- text: `Failed to upload file: ${this.formatError(error)}`,
2691
- }],
2692
- isError: true,
2693
- };
2694
- }
2695
- }
2696
- async searchContent(args) {
2697
- const results = [];
2698
- const types = args.types || Object.keys(this.glossary);
2699
- // Limit to 5 types to avoid too many requests
2700
- for (const type of types.slice(0, 5)) {
2701
- try {
2702
- const response = await this.axiosInstance.post(`/content/${type}/list`, {
2703
- search: args.query,
2704
- page: { size: args.limit || 20, index: 1 },
2705
- });
2706
- if (response.data.items && response.data.items.length > 0) {
2707
- results.push({
2708
- type,
2709
- items: response.data.items,
2710
- total: response.data.total,
2711
- });
2712
- }
2713
- }
2714
- catch (error) {
2715
- // Skip types that error (might not exist or no permission)
2716
- continue;
2717
- }
2718
- }
2719
- return {
2720
- content: [{
2721
- type: 'text',
2722
- text: JSON.stringify(results, null, 2),
2723
- }],
2724
- };
2725
- }
2726
- async getScopes() {
2727
- try {
2728
- const response = await this.axiosInstance.get('/scope/tree');
2729
- return {
2730
- content: [{
2731
- type: 'text',
2732
- text: JSON.stringify(response.data, null, 2),
2733
- }],
2734
- };
2735
- }
2736
- catch (error) {
2737
- return {
2738
- content: [{
2739
- type: 'text',
2740
- text: `Failed to get scopes: ${this.formatError(error)}`,
2741
- }],
2742
- isError: true,
2743
- };
2744
- }
2745
- }
2746
- async getSmartlist(args) {
2747
- try {
2748
- const response = await this.axiosInstance.get(`/smartlist/${args.id}`);
2749
- return {
2750
- content: [{
2751
- type: 'text',
2752
- text: JSON.stringify(response.data, null, 2),
2753
- }],
2754
- };
2755
- }
2756
- catch (error) {
2757
- return {
2758
- content: [{
2759
- type: 'text',
2760
- text: `Failed to get smartlist ${args.id}: ${this.formatError(error)}`,
2761
- }],
2762
- isError: true,
2763
- };
2764
- }
2765
- }
2766
- // Documentation-focused tool implementations
2767
- async getDocumentation(args) {
2768
- try {
2769
- const documentation = QikDocumentationHelper.getTopicDocumentation(args.topic);
2770
- if (!documentation) {
2771
- return {
2772
- content: [{
2773
- type: 'text',
2774
- text: `❌ **DOCUMENTATION NOT FOUND**
2775
-
2776
- No documentation found for topic: "${args.topic}"
2777
-
2778
- **Available topics:**
2779
- - authentication (token types, methods, error codes)
2780
- - endpoints (API endpoint documentation)
2781
- - contentTypes (content type specific guidance)
2782
- - filterSyntax (filter operators and comparators)
2783
- - concepts (key concepts like field placement, scopes, workflows)
2784
- - examples (code examples for common use cases)
2785
- - troubleshooting (error resolution guides)
2786
-
2787
- **Example usage:**
2788
- - \`qik_get_documentation\` with topic: "concepts.field_placement"
2789
- - \`qik_get_documentation\` with topic: "authentication.tokenTypes"
2790
- - \`qik_get_documentation\` with topic: "troubleshooting.field_placement_errors"`,
2791
- }],
2792
- isError: true,
2793
- };
2794
- }
2795
- let result = `📖 **DOCUMENTATION: ${args.topic.toUpperCase()}**\n\n`;
2796
- if (args.subtopic && documentation[args.subtopic]) {
2797
- result += `**Subtopic: ${args.subtopic}**\n\n`;
2798
- result += JSON.stringify(documentation[args.subtopic], null, 2);
2799
- }
2800
- else {
2801
- result += JSON.stringify(documentation, null, 2);
2802
- }
2803
- return {
2804
- content: [{
2805
- type: 'text',
2806
- text: result,
2807
- }],
2808
- };
2809
- }
2810
- catch (error) {
2811
- return {
2812
- content: [{
2813
- type: 'text',
2814
- text: `Failed to get documentation for ${args.topic}: ${this.formatError(error)}`,
2815
- }],
2816
- isError: true,
2817
- };
2818
- }
2819
- }
2820
- async searchDocumentation(args) {
2821
- try {
2822
- const results = QikDocumentationHelper.searchDocumentation(args.query);
2823
- const limit = args.limit || 10;
2824
- const limitedResults = results.slice(0, limit);
2825
- if (limitedResults.length === 0) {
2826
- return {
2827
- content: [{
2828
- type: 'text',
2829
- text: `🔍 **NO DOCUMENTATION FOUND**
2830
-
2831
- No documentation found for query: "${args.query}"
2832
-
2833
- **Try searching for:**
2834
- - "field placement" - for field structure guidance
2835
- - "authentication" - for token and auth help
2836
- - "filter" - for filtering and query syntax
2837
- - "workflow" - for workflow system concepts
2838
- - "birthday" - for anniversary/birthday queries
2839
- - "scope" - for permission and scope guidance
2840
- - "troubleshooting" - for error resolution
2841
-
2842
- **Available documentation sections:**
2843
- - Authentication & Tokens
2844
- - API Endpoints
2845
- - Content Types
2846
- - Filter Syntax
2847
- - Core Concepts
2848
- - Code Examples
2849
- - Troubleshooting Guides`,
2850
- }],
2851
- };
2852
- }
2853
- let result = `🔍 **DOCUMENTATION SEARCH RESULTS**\n\n`;
2854
- result += `Found ${limitedResults.length} results for "${args.query}":\n\n`;
2855
- limitedResults.forEach((item, index) => {
2856
- result += `**${index + 1}. ${item.path}** (${item.type})\n`;
2857
- if (typeof item.content === 'string') {
2858
- const preview = item.content.length > 200
2859
- ? item.content.substring(0, 200) + '...'
2860
- : item.content;
2861
- result += `${preview}\n\n`;
2862
- }
2863
- else {
2864
- result += `${JSON.stringify(item.content).substring(0, 200)}...\n\n`;
2865
- }
2866
- });
2867
- result += `💡 **Need more specific help?** Use \`qik_get_documentation\` with a specific topic for detailed information.`;
2868
- return {
2869
- content: [{
2870
- type: 'text',
2871
- text: result,
2872
- }],
2873
- };
2874
- }
2875
- catch (error) {
2876
- return {
2877
- content: [{
2878
- type: 'text',
2879
- text: `Failed to search documentation: ${this.formatError(error)}`,
2880
- }],
2881
- isError: true,
2882
- };
2883
- }
2884
- }
2885
- async getTroubleshooting(args) {
2886
- try {
2887
- let troubleshootingInfo;
2888
- if (args.issueType) {
2889
- // Get specific issue type
2890
- troubleshootingInfo = QIK_DOCUMENTATION.troubleshooting[args.issueType];
2891
- if (troubleshootingInfo) {
2892
- troubleshootingInfo = { issue: args.issueType, ...troubleshootingInfo };
2893
- }
2894
- }
2895
- else {
2896
- // Auto-detect issue type from error message
2897
- troubleshootingInfo = QikDocumentationHelper.getTroubleshootingInfo(args.errorMessage);
2898
- }
2899
- if (!troubleshootingInfo) {
2900
- return {
2901
- content: [{
2902
- type: 'text',
2903
- text: `🔧 **NO SPECIFIC TROUBLESHOOTING FOUND**
2904
-
2905
- I couldn't find specific troubleshooting information for: "${args.errorMessage}"
2906
-
2907
- **Available troubleshooting categories:**
2908
- - **field_placement_errors**: Field structure and placement issues
2909
- - **authentication_failures**: Token and permission problems
2910
- - **filter_syntax_errors**: Query and filter syntax issues
2911
- - **scope_permission_issues**: Access and permission problems
2912
- - **workflow_confusion**: Workflow definition vs card confusion
2913
- - **date_handling_issues**: Date format and timezone problems
2914
- - **performance_issues**: Slow queries and optimization
2915
-
2916
- **General troubleshooting steps:**
2917
- 1. Check the error message for specific field names or codes
2918
- 2. Verify your access token and permissions
2919
- 3. Ensure proper field placement (root vs data object)
2920
- 4. Validate filter syntax and comparators
2921
- 5. Check scope permissions and hierarchy
2922
-
2923
- **Need more help?** Try:
2924
- - \`qik_search_documentation\` with keywords from your error
2925
- - \`qik_get_documentation\` with topic: "troubleshooting"
2926
- - \`qik_validate_field_placement\` for content creation issues`,
2927
- }],
2928
- };
2929
- }
2930
- let result = `🔧 **TROUBLESHOOTING: ${troubleshootingInfo.issue.toUpperCase().replace(/_/g, ' ')}**\n\n`;
2931
- result += `**Your Error:** "${args.errorMessage}"\n\n`;
2932
- if (troubleshootingInfo.symptoms && troubleshootingInfo.symptoms.length > 0) {
2933
- result += `**Common Symptoms:**\n`;
2934
- result += troubleshootingInfo.symptoms.map((s) => `- ${s}`).join('\n');
2935
- result += '\n\n';
2936
- }
2937
- if (troubleshootingInfo.causes && troubleshootingInfo.causes.length > 0) {
2938
- result += `**Likely Causes:**\n`;
2939
- result += troubleshootingInfo.causes.map((c) => `- ${c}`).join('\n');
2940
- result += '\n\n';
2941
- }
2942
- if (troubleshootingInfo.solutions && troubleshootingInfo.solutions.length > 0) {
2943
- result += `**Solutions:**\n`;
2944
- result += troubleshootingInfo.solutions.map((s) => `- ${s}`).join('\n');
2945
- result += '\n\n';
2946
- }
2947
- if (troubleshootingInfo.prevention && troubleshootingInfo.prevention.length > 0) {
2948
- result += `**Prevention:**\n`;
2949
- result += troubleshootingInfo.prevention.map((p) => `- ${p}`).join('\n');
2950
- result += '\n\n';
2951
- }
2952
- if (troubleshootingInfo.relatedIssues && troubleshootingInfo.relatedIssues.length > 0) {
2953
- result += `**Related Issues:**\n`;
2954
- result += troubleshootingInfo.relatedIssues.map((r) => `- ${r}`).join('\n');
2955
- }
2956
- return {
2957
- content: [{
2958
- type: 'text',
2959
- text: result,
2960
- }],
2961
- };
2962
- }
2963
- catch (error) {
2964
- return {
2965
- content: [{
2966
- type: 'text',
2967
- text: `Failed to get troubleshooting information: ${this.formatError(error)}`,
2968
- }],
2969
- isError: true,
2970
- };
2971
- }
2972
- }
2973
- async getExamples(args) {
616
+ async deleteContent(args) {
2974
617
  try {
2975
- if (args.category === 'all') {
2976
- let result = `💡 **ALL CODE EXAMPLES**\n\n`;
2977
- for (const [category, examples] of Object.entries(QIK_DOCUMENTATION.examples)) {
2978
- result += `## ${category.toUpperCase()}\n\n`;
2979
- for (const [exampleKey, example] of Object.entries(examples)) {
2980
- const typedExample = example;
2981
- result += `### ${typedExample.title}\n`;
2982
- result += `${typedExample.description}\n\n`;
2983
- result += `\`\`\`json\n${JSON.stringify(typedExample.code, null, 2)}\n\`\`\`\n\n`;
2984
- result += `**Explanation:** ${typedExample.explanation}\n\n`;
2985
- if (typedExample.variations && typedExample.variations.length > 0) {
2986
- result += `**Variations:**\n`;
2987
- typedExample.variations.forEach((variation) => {
2988
- result += `- ${variation.description}\n`;
2989
- result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
2990
- });
2991
- result += '\n';
2992
- }
2993
- }
2994
- }
2995
- return {
2996
- content: [{
2997
- type: 'text',
2998
- text: result,
2999
- }],
3000
- };
3001
- }
3002
- const examples = QikDocumentationHelper.getExamples(args.category, args.example);
3003
- if (!examples) {
3004
- return {
3005
- content: [{
3006
- type: 'text',
3007
- text: `💡 **NO EXAMPLES FOUND**
3008
-
3009
- No examples found for category: "${args.category}"
3010
-
3011
- **Available example categories:**
3012
- - **authentication**: Token usage and auth examples
3013
- - **content_creation**: Creating different content types
3014
- - **filtering**: Advanced filtering and search examples
3015
-
3016
- **Example usage:**
3017
- - \`qik_get_examples\` with category: "authentication"
3018
- - \`qik_get_examples\` with category: "filtering" and example: "birthday_search"
3019
- - \`qik_get_examples\` with category: "all" (shows all examples)`,
3020
- }],
3021
- isError: true,
3022
- };
3023
- }
3024
- let result = `💡 **CODE EXAMPLES: ${args.category.toUpperCase()}**\n\n`;
3025
- if (args.example && examples[args.example]) {
3026
- // Show specific example
3027
- const example = examples[args.example];
3028
- result += `### ${example.title}\n`;
3029
- result += `${example.description}\n\n`;
3030
- result += `\`\`\`json\n${JSON.stringify(example.code, null, 2)}\n\`\`\`\n\n`;
3031
- result += `**Explanation:** ${example.explanation}\n\n`;
3032
- if (example.variations && example.variations.length > 0) {
3033
- result += `**Variations:**\n`;
3034
- example.variations.forEach((variation) => {
3035
- result += `- ${variation.description}\n`;
3036
- result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
3037
- });
3038
- }
3039
- }
3040
- else {
3041
- // Show all examples in category
3042
- for (const [exampleKey, example] of Object.entries(examples)) {
3043
- const typedExample = example;
3044
- result += `### ${typedExample.title}\n`;
3045
- result += `${typedExample.description}\n\n`;
3046
- result += `\`\`\`json\n${JSON.stringify(typedExample.code, null, 2)}\n\`\`\`\n\n`;
3047
- result += `**Explanation:** ${typedExample.explanation}\n\n`;
3048
- if (typedExample.variations && typedExample.variations.length > 0) {
3049
- result += `**Variations:**\n`;
3050
- typedExample.variations.forEach((variation) => {
3051
- result += `- ${variation.description}\n`;
3052
- result += ` \`\`\`json\n ${JSON.stringify(variation.code, null, 2)}\n \`\`\`\n`;
3053
- });
3054
- result += '\n';
3055
- }
3056
- }
3057
- }
618
+ await this.axiosInstance.delete(`/content/${args.id}`);
3058
619
  return {
3059
620
  content: [{
3060
621
  type: 'text',
3061
- text: result,
622
+ text: `✅ Successfully deleted content ${args.id}`,
3062
623
  }],
3063
624
  };
3064
625
  }
3065
626
  catch (error) {
3066
- return {
3067
- content: [{
3068
- type: 'text',
3069
- text: `Failed to get examples: ${this.formatError(error)}`,
3070
- }],
3071
- isError: true,
3072
- };
3073
- }
3074
- }
3075
- async validateFieldPlacement(args) {
3076
- try {
3077
- const validation = this.validateContentType(args.contentType);
3078
- if (!validation.valid) {
3079
- return {
3080
- content: [{
3081
- type: 'text',
3082
- text: validation.error,
3083
- }],
3084
- isError: true,
3085
- };
3086
- }
3087
- // Simplified validation using documentation helper only
3088
- const docValidation = QikDocumentationHelper.validateFieldPlacement(args.contentType, args.payload);
3089
- let result = `✅ **FIELD PLACEMENT VALIDATION: ${args.contentType.toUpperCase()}**\n\n`;
3090
- if (docValidation.valid) {
3091
- result += `🎉 **VALIDATION PASSED**\n\n`;
3092
- result += `Your payload has correct field placement!\n\n`;
3093
- result += `**Field Analysis:**\n`;
3094
- result += `- Root level fields: ${Object.keys(args.payload).filter(k => k !== 'data' && k !== 'meta').join(', ') || 'none'}\n`;
3095
- result += `- Data object fields: ${args.payload.data ? Object.keys(args.payload.data).join(', ') : 'none'}\n`;
3096
- result += `- Meta fields: ${args.payload.meta ? Object.keys(args.payload.meta).join(', ') : 'none'}\n\n`;
3097
- }
3098
- else {
3099
- result += `❌ **VALIDATION FAILED**\n\n`;
3100
- if (docValidation.errors && docValidation.errors.length > 0) {
3101
- result += `**Errors Found:**\n`;
3102
- docValidation.errors.forEach(error => {
3103
- result += `- ${error}\n`;
3104
- });
3105
- result += '\n';
3106
- }
3107
- result += `**General Field Placement Rules:**\n`;
3108
- result += `- **Root Level**: title, reference, referenceType, body, organisation, meta\n`;
3109
- result += `- **Data Object**: Custom fields defined in content type's "definedFields" array\n`;
3110
- result += `- Use \`qik_get_content_definition\` to see the exact field structure for this content type\n\n`;
3111
- result += `**Correct Structure Example:**\n`;
3112
- result += `\`\`\`json\n`;
3113
- result += `{\n`;
3114
- result += ` "type": "${args.contentType}",\n`;
3115
- result += ` "title": "Your Title Here",\n`;
3116
- result += ` "meta": { "scopes": ["scope-id-here"] },\n`;
3117
- result += ` "data": {\n`;
3118
- result += ` "customField": "value"\n`;
3119
- result += ` }\n`;
3120
- result += `}\n\`\`\`\n`;
627
+ if (axios.isAxiosError(error)) {
628
+ return await this.handleApiError(error, 'delete content', args);
3121
629
  }
3122
- return {
3123
- content: [{
3124
- type: 'text',
3125
- text: result,
3126
- }],
3127
- isError: !docValidation.valid,
3128
- };
3129
- }
3130
- catch (error) {
3131
- return {
3132
- content: [{
3133
- type: 'text',
3134
- text: `Failed to validate field placement: ${this.formatError(error)}`,
3135
- }],
3136
- isError: true,
3137
- };
630
+ throw error;
3138
631
  }
3139
632
  }
3140
633
  async run() {
3141
634
  const transport = new StdioServerTransport();
3142
635
  await this.server.connect(transport);
3143
- this.log('Qik MCP server running on stdio');
636
+ this.log('Simplified Qik MCP server running on stdio');
3144
637
  }
3145
638
  }
3146
- // Only run the server if this file is executed directly (not imported)
639
+ // Only run the server if this file is executed directly
3147
640
  if (import.meta.url === `file://${process.argv[1]}`) {
3148
641
  const server = new QikMCPServer();
3149
642
  server.run().catch(console.error);