@qikdev/mcp 6.6.6 → 6.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,6 +49,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } f
49
49
  import axios from 'axios';
50
50
  import FormData from 'form-data';
51
51
  import { ConfigManager } from './config.js';
52
+ import { QikDocumentationHelper, QIK_DOCUMENTATION } from './documentation.js';
52
53
  // Environment variables
53
54
  const QIK_API_URL = process.env.QIK_API_URL || 'https://api.qik.dev';
54
55
  const QIK_ACCESS_TOKEN = process.env.QIK_ACCESS_TOKEN;
@@ -199,7 +200,7 @@ export class QikMCPServer {
199
200
  }
200
201
  return { valid: true, info };
201
202
  }
202
- // Helper methods for field analysis and processing
203
+ // Enhanced glossary-driven field analysis methods
203
204
  isFieldRequired(field) {
204
205
  return field.minimum !== undefined && field.minimum > 0;
205
206
  }
@@ -209,6 +210,114 @@ export class QikMCPServer {
209
210
  getMinimumArrayLength(field) {
210
211
  return this.isFieldArray(field) ? (field.minimum || 0) : 0;
211
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
+ }
212
321
  generateFieldPath(field, isDefinedField, groupPath = '') {
213
322
  let basePath = isDefinedField ? 'data' : '';
214
323
  if (groupPath) {
@@ -363,57 +472,83 @@ export class QikMCPServer {
363
472
  return 'string';
364
473
  }
365
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
+ */
366
479
  generateDynamicContentProperties() {
367
- // Generate dynamic properties that show field structure for common content types
368
480
  const properties = {};
369
- // Add common root-level fields that appear across content types
370
- const commonRootFields = ['reference', 'referenceType', 'body', 'organisation'];
371
- for (const fieldKey of commonRootFields) {
372
- properties[fieldKey] = {
373
- type: 'string',
374
- description: `${fieldKey} field (ALWAYS goes at ROOT LEVEL, never in data object)`,
375
- };
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
+ }
376
511
  }
377
- // Add a data object for definedFields with clear explanation
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
+ };
533
+ }
534
+ }
535
+ // Add the data object with dynamic properties
378
536
  properties.data = {
379
537
  type: 'object',
380
- description: `CRITICAL FIELD PLACEMENT RULE:
538
+ description: `DYNAMIC FIELD PLACEMENT (based on glossary):
381
539
 
382
- **ROOT LEVEL FIELDS (never put these in data object):**
383
- - title, reference, referenceType, body, organisation, meta
384
- - These are "fields" in the glossary definition
540
+ **ROOT LEVEL FIELDS (from glossary "fields" array):**
541
+ ${Array.from(allRootFields).sort().join(', ')}
385
542
 
386
- **DATA OBJECT FIELDS (always put these in data object):**
387
- - Custom fields defined as "definedFields" in the glossary
388
- - Content-type specific fields like wasAnyoneHurt, customField1, etc.
543
+ **DATA OBJECT FIELDS (from glossary "definedFields" array):**
544
+ ${Array.from(allDataFields).sort().join(', ')}
389
545
 
390
- **WRONG EXAMPLE:**
391
- {
392
- "data": {
393
- "title": "My Title", // ❌ WRONG - title goes at root level
394
- "plural": "My Items", // ❌ WRONG - plural goes at root level
395
- "customField": "value"
396
- }
397
- }
546
+ **FIELD PLACEMENT RULE:**
547
+ - Fields in glossary "fields" array → ROOT LEVEL
548
+ - Fields in glossary "definedFields" array → DATA OBJECT
398
549
 
399
- **CORRECT EXAMPLE:**
400
- {
401
- "title": "My Title", // ✅ CORRECT - at root level
402
- "data": {
403
- "customField": "value" // ✅ CORRECT - custom fields in data object
404
- }
405
- }`,
406
- properties: {
407
- wasAnyoneHurt: {
408
- type: 'string',
409
- description: 'For incident reports - whether anyone was hurt (this is a definedField, so it goes in data object)',
410
- enum: ['yes', 'no']
411
- },
412
- whatTimeDidItHappen: {
413
- type: 'string',
414
- description: 'For incident reports - when the incident occurred (this is a definedField, so it goes in data object)'
415
- }
416
- }
550
+ The MCP server automatically determines correct placement based on the glossary definition for each content type.`,
551
+ properties: dataProperties
417
552
  };
418
553
  return properties;
419
554
  }
@@ -1638,6 +1773,46 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1638
1773
  required: ['firstName', 'lastName'],
1639
1774
  },
1640
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
+ },
1641
1816
  // Form Management
1642
1817
  {
1643
1818
  name: 'qik_get_form',
@@ -1863,6 +2038,10 @@ Use \`qik_get_content_definition\` with any of the type keys above to see their
1863
2038
  return await this.getScopes();
1864
2039
  case 'qik_get_smartlist':
1865
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);
1866
2045
  case 'qik_create_content_intelligent':
1867
2046
  return await this.intelligentContentCreation(request.params.arguments.description, {
1868
2047
  title: request.params.arguments.title,
@@ -2124,175 +2303,50 @@ ${JSON.stringify({
2124
2303
  isError: true,
2125
2304
  };
2126
2305
  }
2127
- // Get content type info to understand field structure
2128
- const contentTypeInfo = validation.info;
2129
- let fields = [];
2130
- let definedFields = [];
2131
- let baseType = null;
2132
- // Extract fields and definedFields from the content type definition
2133
- if (contentTypeInfo.fields) {
2134
- fields = contentTypeInfo.fields;
2135
- definedFields = contentTypeInfo.definedFields || [];
2136
- baseType = contentTypeInfo.definesType;
2137
- }
2138
- else if (contentTypeInfo.definition) {
2139
- fields = contentTypeInfo.definition.fields || [];
2140
- definedFields = contentTypeInfo.definition.definedFields || [];
2141
- baseType = contentTypeInfo.definition.definesType;
2142
- }
2143
- else if (contentTypeInfo.type) {
2144
- fields = contentTypeInfo.type.fields || [];
2145
- definedFields = contentTypeInfo.type.definedFields || [];
2146
- baseType = contentTypeInfo.type.definesType;
2147
- }
2148
- // ENHANCED FIELD PLACEMENT VALIDATION
2149
- const fieldPlacementErrors = [];
2150
- const rootFieldKeys = new Set(fields.map(f => f.key));
2151
- const dataFieldKeys = new Set(definedFields.map(f => f.key));
2152
- // Check for common misplaced fields in data object
2153
- if (args.data && typeof args.data === 'object') {
2154
- const commonRootFields = ['title', 'plural', 'reference', 'referenceType', 'body', 'organisation'];
2155
- for (const fieldKey of commonRootFields) {
2156
- if (args.data[fieldKey] !== undefined) {
2157
- fieldPlacementErrors.push(`❌ FIELD PLACEMENT ERROR: "${fieldKey}" should be at ROOT LEVEL, not in data object`);
2158
- }
2159
- }
2160
- // Check for fields that should be at root level but are in data
2161
- for (const [key, value] of Object.entries(args.data)) {
2162
- if (rootFieldKeys.has(key)) {
2163
- fieldPlacementErrors.push(`❌ FIELD PLACEMENT ERROR: "${key}" is defined as a "field" in the glossary and should be at ROOT LEVEL, not in data object`);
2164
- }
2165
- }
2166
- }
2167
- // Check for fields that should be in data object but are at root level
2168
- for (const [key, value] of Object.entries(args)) {
2169
- if (key !== 'type' && key !== 'title' && key !== 'meta' && key !== 'data' &&
2170
- key !== 'reference' && key !== 'referenceType' && key !== 'body' && key !== 'organisation') {
2171
- if (dataFieldKeys.has(key)) {
2172
- fieldPlacementErrors.push(`❌ FIELD PLACEMENT ERROR: "${key}" is defined as a "definedField" in the glossary and should be in the DATA OBJECT, not at root level`);
2173
- }
2174
- }
2175
- }
2176
- // If there are field placement errors, return detailed guidance
2177
- if (fieldPlacementErrors.length > 0) {
2178
- const rootFieldsList = Array.from(rootFieldKeys).join(', ');
2179
- const dataFieldsList = Array.from(dataFieldKeys).join(', ');
2180
- return {
2181
- content: [{
2182
- type: 'text',
2183
- text: `🚫 **FIELD PLACEMENT ERRORS DETECTED**
2184
-
2185
- ${fieldPlacementErrors.join('\n')}
2186
-
2187
- **CORRECT FIELD PLACEMENT FOR ${args.type.toUpperCase()}:**
2188
-
2189
- **ROOT LEVEL FIELDS (never put in data object):**
2190
- - Standard fields: title, reference, referenceType, body, organisation, meta
2191
- - Content type "fields": ${rootFieldsList || 'none defined'}
2192
-
2193
- **DATA OBJECT FIELDS (always put in data object):**
2194
- - Content type "definedFields": ${dataFieldsList || 'none defined'}
2195
-
2196
- **CORRECT STRUCTURE EXAMPLE:**
2197
- \`\`\`json
2198
- {
2199
- "type": "${args.type}",
2200
- "title": "Your Title Here",
2201
- "reference": "optional-reference-id",
2202
- "referenceType": "optional-reference-type",
2203
- "meta": {
2204
- "scopes": ["scope-id-here"]
2205
- },
2206
- "data": {
2207
- ${Array.from(dataFieldKeys).map(field => `"${field}": "your-value-here"`).join(',\n ')}
2208
- }
2209
- }
2210
- \`\`\`
2211
-
2212
- **WHAT TO FIX:**
2213
- 1. Move any fields mentioned in the errors above to their correct location
2214
- 2. Use qik_get_content_definition with type "${args.type}" to see the complete field structure
2215
- 3. Remember: "fields" go at root level, "definedFields" go in data object`,
2216
- }],
2217
- isError: true,
2218
- };
2219
- }
2220
- // Structure payload correctly based on field definitions
2221
- const payload = {
2222
- title: args.title,
2223
- meta: args.meta || {},
2224
- };
2225
- // Separate fields into root level vs data object
2226
- const rootLevelData = {};
2227
- const dataObjectData = {};
2228
- // Handle direct field assignments from args (for fields like reference, referenceType, body)
2229
- const directFields = ['reference', 'referenceType', 'body', 'organisation'];
2230
- for (const fieldKey of directFields) {
2231
- if (args[fieldKey] !== undefined) {
2232
- rootLevelData[fieldKey] = args[fieldKey];
2233
- }
2234
- }
2235
- if (args.data && typeof args.data === 'object') {
2236
- // Separate the provided data based on field definitions
2237
- for (const [key, value] of Object.entries(args.data)) {
2238
- if (rootFieldKeys.has(key)) {
2239
- // This field should go at root level (from 'fields')
2240
- rootLevelData[key] = value;
2241
- }
2242
- else if (dataFieldKeys.has(key)) {
2243
- // This field should go in data object (from 'definedFields')
2244
- dataObjectData[key] = value;
2306
+ // Simplified approach - send data as provided and let server handle validation
2307
+ 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
+ }
2245
2322
  }
2246
- else {
2247
- // Unknown field - put in data object as fallback
2248
- dataObjectData[key] = value;
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
2249
2326
  }
2250
2327
  }
2251
- }
2252
- // Add root level fields to payload
2253
- Object.assign(payload, rootLevelData);
2254
- // Add data object fields if any exist
2255
- if (Object.keys(dataObjectData).length > 0) {
2256
- payload.data = dataObjectData;
2257
- }
2258
- // Handle scope inheritance for comment types
2259
- const isCommentType = baseType === 'comment' || args.type === 'comment' || args.type.includes('Comment');
2260
- if (isCommentType && payload.reference && (!payload.meta.scopes || payload.meta.scopes.length === 0)) {
2261
- try {
2262
- // Fetch the referenced item to get its scopes
2263
- const referencedItem = await this.axiosInstance.get(`/content/${payload.reference}`);
2264
- if (referencedItem.data && referencedItem.data.meta && referencedItem.data.meta.scopes) {
2265
- payload.meta.scopes = referencedItem.data.meta.scopes;
2266
- this.log(`Inherited scopes from referenced item ${payload.reference}: ${payload.meta.scopes.join(', ')}`);
2267
- }
2268
- }
2269
- catch (error) {
2270
- this.log(`Failed to fetch referenced item ${payload.reference} for scope inheritance: ${this.formatError(error)}`);
2271
- // Continue without scope inheritance if we can't fetch the referenced item
2272
- }
2273
- }
2274
- try {
2275
2328
  const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
2276
2329
  return {
2277
2330
  content: [{
2278
2331
  type: 'text',
2279
2332
  text: `✅ **SUCCESSFULLY CREATED ${args.type.toUpperCase()} CONTENT**
2280
2333
 
2281
- The content was created with proper field placement:
2282
- - Root level fields: ${Object.keys(payload).filter(k => k !== 'data' && k !== 'meta').join(', ')}
2283
- - Data object fields: ${payload.data ? Object.keys(payload.data).join(', ') : 'none'}
2284
- - Meta fields: ${Object.keys(payload.meta).join(', ')}
2285
-
2286
2334
  **Created Content:**
2287
2335
  ${JSON.stringify(response.data, null, 2)}`,
2288
2336
  }],
2289
2337
  };
2290
2338
  }
2291
2339
  catch (error) {
2340
+ // Pass through server errors directly - they contain the authoritative validation messages
2292
2341
  return {
2293
2342
  content: [{
2294
2343
  type: 'text',
2295
- text: `Failed to create ${args.type}: ${this.formatError(error)}`,
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`,
2296
2350
  }],
2297
2351
  isError: true,
2298
2352
  };
@@ -2391,6 +2445,180 @@ ${JSON.stringify(response.data, null, 2)}`,
2391
2445
  };
2392
2446
  }
2393
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.
2457
+
2458
+ **Timeline Data:**
2459
+ ${JSON.stringify(response.data, null, 2)}`,
2460
+ }],
2461
+ };
2462
+ }
2463
+ 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
+ };
2604
+ }
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
+ };
2620
+ }
2621
+ }
2394
2622
  async getForm(args) {
2395
2623
  try {
2396
2624
  const response = await this.axiosInstance.get(`/form/${args.id}`);
@@ -2535,6 +2763,380 @@ ${JSON.stringify(response.data, null, 2)}`,
2535
2763
  };
2536
2764
  }
2537
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) {
2974
+ 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
+ }
3058
+ return {
3059
+ content: [{
3060
+ type: 'text',
3061
+ text: result,
3062
+ }],
3063
+ };
3064
+ }
3065
+ 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`;
3121
+ }
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
+ };
3138
+ }
3139
+ }
2538
3140
  async run() {
2539
3141
  const transport = new StdioServerTransport();
2540
3142
  await this.server.connect(transport);