@qikdev/mcp 6.6.2 → 6.6.5

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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Qik Platform MCP Server - Enhanced Version
3
+ * Qik Platform MCP Server - Enhanced Version with Comprehensive Documentation
4
4
  *
5
5
  * This MCP server provides comprehensive integration with the Qik platform,
6
6
  * enabling AI assistants to interact with Qik's content management system,
@@ -13,12 +13,42 @@
13
13
  * - Dynamic tool schema generation
14
14
  * - Comprehensive API coverage
15
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
16
45
  */
17
46
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
47
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
48
  import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
20
49
  import axios from 'axios';
21
50
  import FormData from 'form-data';
51
+ import { ConfigManager } from './config.js';
22
52
  // Environment variables
23
53
  const QIK_API_URL = process.env.QIK_API_URL || 'https://api.qik.dev';
24
54
  const QIK_ACCESS_TOKEN = process.env.QIK_ACCESS_TOKEN;
@@ -29,6 +59,7 @@ export class QikMCPServer {
29
59
  userSession = null;
30
60
  lastGlossaryUpdate = 0;
31
61
  GLOSSARY_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
62
+ serverName = 'Qik'; // Default fallback
32
63
  constructor() {
33
64
  if (!QIK_ACCESS_TOKEN) {
34
65
  throw new Error('QIK_ACCESS_TOKEN environment variable is required. Run "qik-mcp-server setup" to configure.');
@@ -68,7 +99,9 @@ export class QikMCPServer {
68
99
  }
69
100
  async initializeServer() {
70
101
  try {
71
- // Load user session first
102
+ // Load server name from config first
103
+ await this.loadServerName();
104
+ // Load user session
72
105
  await this.loadUserSession();
73
106
  // Then load glossary
74
107
  await this.loadGlossary();
@@ -78,6 +111,23 @@ export class QikMCPServer {
78
111
  this.log(`Failed to initialize server: ${this.formatError(error)}`);
79
112
  }
80
113
  }
114
+ async loadServerName() {
115
+ try {
116
+ const configManager = new ConfigManager();
117
+ const config = await configManager.loadConfig();
118
+ if (config && config.serverName) {
119
+ this.serverName = config.serverName;
120
+ this.log(`Loaded server name: ${this.serverName}`);
121
+ }
122
+ else {
123
+ this.log(`No server name found in config, using default: ${this.serverName}`);
124
+ }
125
+ }
126
+ catch (error) {
127
+ this.log(`Failed to load server name from config: ${this.formatError(error)}`);
128
+ // Keep default fallback value
129
+ }
130
+ }
81
131
  async loadUserSession() {
82
132
  try {
83
133
  const response = await this.axiosInstance.get('/user');
@@ -458,161 +508,793 @@ export class QikMCPServer {
458
508
  }
459
509
  return scopes;
460
510
  }
511
+ // Enhanced filter helper functions
512
+ validateFilter(filter) {
513
+ const errors = [];
514
+ if (!filter || typeof filter !== 'object') {
515
+ return { valid: true, errors: [] }; // Empty filter is valid
516
+ }
517
+ // Check if it's a filter group or condition
518
+ if (filter.operator) {
519
+ // Filter group validation
520
+ if (!['and', 'or', 'nor'].includes(filter.operator)) {
521
+ errors.push(`Invalid operator '${filter.operator}'. Must be 'and', 'or', or 'nor'.`);
522
+ }
523
+ if (!Array.isArray(filter.filters)) {
524
+ errors.push('Filter group must have a "filters" array.');
525
+ }
526
+ else {
527
+ // Recursively validate nested filters
528
+ for (const nestedFilter of filter.filters) {
529
+ const nestedValidation = this.validateFilter(nestedFilter);
530
+ errors.push(...nestedValidation.errors);
531
+ }
532
+ }
533
+ }
534
+ else if (filter.key && filter.comparator) {
535
+ // Filter condition validation
536
+ const validComparators = [
537
+ 'dateanniversary', 'anniversarybetween', 'anniversarynext', 'anniversarypast',
538
+ 'datenext', 'datenotbetween', 'datenotnext', 'datebefore', 'dateafter',
539
+ 'datetoday', 'datenottoday', 'datebeforetoday', 'dateaftertoday',
540
+ 'datebeforenow', 'dateafternow', 'datepast', 'datenotpast',
541
+ 'datesameday', 'datesamemonth', 'datesameweek', 'datesameyear',
542
+ 'datebetween', 'datemonth',
543
+ 'equal', 'notequal', 'in', 'notin', 'startswith', 'doesnotstartwith',
544
+ 'endswith', 'doesnotendwith', 'contains', 'excludes',
545
+ 'greater', 'lesser', 'greaterequal', 'lesserequal',
546
+ 'notgreater', 'notlesser', 'notgreaterequal', 'notlesserequal',
547
+ 'between', 'notbetween',
548
+ 'valuesgreater', 'valueslesser', 'valuesgreaterequal', 'valueslesserequal',
549
+ 'empty', 'notempty'
550
+ ];
551
+ if (!validComparators.includes(filter.comparator)) {
552
+ errors.push(`Invalid comparator '${filter.comparator}'.`);
553
+ }
554
+ // Validate required values for specific comparators
555
+ const requiresValue = ['equal', 'notequal', 'greater', 'lesser', 'greaterequal', 'lesserequal', 'contains', 'excludes', 'startswith', 'endswith', 'datebefore', 'dateafter', 'dateanniversary'];
556
+ const requiresValues = ['in', 'notin'];
557
+ const requiresValue2 = ['between', 'notbetween', 'anniversarybetween', 'anniversarynext', 'anniversarypast', 'datenext', 'datepast'];
558
+ if (requiresValue.includes(filter.comparator) && filter.value === undefined) {
559
+ errors.push(`Comparator '${filter.comparator}' requires a 'value' parameter.`);
560
+ }
561
+ if (requiresValues.includes(filter.comparator) && (!filter.values || !Array.isArray(filter.values))) {
562
+ errors.push(`Comparator '${filter.comparator}' requires a 'values' array parameter.`);
563
+ }
564
+ if (requiresValue2.includes(filter.comparator) && filter.value2 === undefined) {
565
+ errors.push(`Comparator '${filter.comparator}' requires both 'value' and 'value2' parameters.`);
566
+ }
567
+ }
568
+ else {
569
+ errors.push('Filter must be either a filter group (with operator and filters) or a condition (with key and comparator).');
570
+ }
571
+ return { valid: errors.length === 0, errors };
572
+ }
573
+ createBirthdayFilter(timeframe, amount, unit = 'days') {
574
+ return {
575
+ operator: 'and',
576
+ filters: [{
577
+ key: 'dob',
578
+ comparator: timeframe === 'next' ? 'anniversarynext' : 'anniversarypast',
579
+ value: amount,
580
+ value2: unit
581
+ }]
582
+ };
583
+ }
584
+ createDateRangeFilter(field, startDate, endDate) {
585
+ return {
586
+ operator: 'and',
587
+ filters: [{
588
+ key: field,
589
+ comparator: 'datebetween',
590
+ value: startDate,
591
+ value2: endDate
592
+ }]
593
+ };
594
+ }
595
+ createGenderFilter(gender) {
596
+ return {
597
+ key: 'gender',
598
+ comparator: 'equal',
599
+ value: gender
600
+ };
601
+ }
602
+ createAgeRangeFilter(minAge, maxAge) {
603
+ const currentYear = new Date().getFullYear();
604
+ return {
605
+ operator: 'and',
606
+ filters: [{
607
+ key: 'dobYear',
608
+ comparator: 'between',
609
+ value: currentYear - maxAge,
610
+ value2: currentYear - minAge
611
+ }]
612
+ };
613
+ }
614
+ createThisMonthBirthdayFilter() {
615
+ const now = new Date();
616
+ const currentMonth = now.getMonth() + 1; // JavaScript months are 0-indexed
617
+ return {
618
+ operator: 'and',
619
+ filters: [{
620
+ key: 'dobMonth',
621
+ comparator: 'equal',
622
+ value: currentMonth
623
+ }]
624
+ };
625
+ }
626
+ createRecentContentFilter(days = 30) {
627
+ return {
628
+ operator: 'and',
629
+ filters: [{
630
+ key: 'meta.created',
631
+ comparator: 'datepast',
632
+ value: days,
633
+ value2: 'days'
634
+ }]
635
+ };
636
+ }
637
+ createScopeFilter(scopeIds) {
638
+ return {
639
+ operator: 'and',
640
+ filters: [{
641
+ key: 'meta.scopes',
642
+ comparator: 'in',
643
+ values: scopeIds
644
+ }]
645
+ };
646
+ }
647
+ generateEnhancedFilterSchema() {
648
+ return {
649
+ type: 'object',
650
+ 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.
651
+
652
+ EXAMPLES:
653
+
654
+ 1. Birthdays in next 10 days:
655
+ {
656
+ "operator": "and",
657
+ "filters": [{
658
+ "key": "dob",
659
+ "comparator": "anniversarynext",
660
+ "value": 10,
661
+ "value2": "days"
662
+ }]
663
+ }
664
+
665
+ 2. Male profiles born this month:
666
+ {
667
+ "operator": "and",
668
+ "filters": [
669
+ {"key": "gender", "comparator": "equal", "value": "male"},
670
+ {"key": "dobMonth", "comparator": "equal", "value": 8}
671
+ ]
672
+ }
673
+
674
+ 3. Content created in last 30 days:
675
+ {
676
+ "operator": "and",
677
+ "filters": [{
678
+ "key": "meta.created",
679
+ "comparator": "datepast",
680
+ "value": 30,
681
+ "value2": "days"
682
+ }]
683
+ }
684
+
685
+ 4. Complex query with OR logic:
686
+ {
687
+ "operator": "or",
688
+ "filters": [
689
+ {"key": "firstName", "comparator": "startswith", "value": "John"},
690
+ {"key": "lastName", "comparator": "contains", "value": "Smith"}
691
+ ]
692
+ }`,
693
+ properties: {
694
+ operator: {
695
+ type: 'string',
696
+ enum: ['and', 'or', 'nor'],
697
+ description: 'Logical operator: "and" (all must match), "or" (any can match), "nor" (none can match)'
698
+ },
699
+ filters: {
700
+ type: 'array',
701
+ description: 'Array of filter conditions or nested filter groups',
702
+ items: {
703
+ oneOf: [
704
+ {
705
+ type: 'object',
706
+ description: 'Filter condition',
707
+ properties: {
708
+ key: {
709
+ type: 'string',
710
+ description: 'Field path to filter on (e.g., "firstName", "meta.created", "data.customField")'
711
+ },
712
+ comparator: {
713
+ type: 'string',
714
+ enum: [
715
+ // Date/Anniversary comparators
716
+ 'dateanniversary', 'anniversarybetween', 'anniversarynext', 'anniversarypast',
717
+ 'datenext', 'datenotbetween', 'datenotnext', 'datebefore', 'dateafter',
718
+ 'datetoday', 'datenottoday', 'datebeforetoday', 'dateaftertoday',
719
+ 'datebeforenow', 'dateafternow', 'datepast', 'datenotpast',
720
+ 'datesameday', 'datesamemonth', 'datesameweek', 'datesameyear',
721
+ 'datebetween', 'datemonth',
722
+ // String comparators
723
+ 'equal', 'notequal', 'in', 'notin', 'startswith', 'doesnotstartwith',
724
+ 'endswith', 'doesnotendwith', 'contains', 'excludes',
725
+ // Numeric comparators
726
+ 'greater', 'lesser', 'greaterequal', 'lesserequal',
727
+ 'notgreater', 'notlesser', 'notgreaterequal', 'notlesserequal',
728
+ 'between', 'notbetween',
729
+ // Array/value comparators
730
+ 'valuesgreater', 'valueslesser', 'valuesgreaterequal', 'valueslesserequal',
731
+ 'empty', 'notempty'
732
+ ],
733
+ description: 'Comparison operator - see documentation for full list and usage'
734
+ },
735
+ value: {
736
+ description: 'Primary comparison value (type depends on comparator)'
737
+ },
738
+ value2: {
739
+ description: 'Secondary value for range comparators (between, anniversarynext, etc.)'
740
+ },
741
+ values: {
742
+ type: 'array',
743
+ description: 'Array of values for "in" and "notin" comparators'
744
+ }
745
+ },
746
+ required: ['key', 'comparator']
747
+ },
748
+ {
749
+ type: 'object',
750
+ description: 'Nested filter group',
751
+ properties: {
752
+ operator: { type: 'string', enum: ['and', 'or', 'nor'] },
753
+ filters: { type: 'array' }
754
+ },
755
+ required: ['operator', 'filters']
756
+ }
757
+ ]
758
+ }
759
+ }
760
+ }
761
+ };
762
+ }
763
+ /**
764
+ * Enhanced intelligent content creation with advanced disambiguation logic
765
+ *
766
+ * This method provides sophisticated analysis of user intent to distinguish between:
767
+ * - Creating workflow DEFINITIONS vs workflow CARD instances
768
+ * - Creating content type DEFINITIONS vs content INSTANCES
769
+ * - Understanding context clues like "add person to workflow" vs "create new workflow"
770
+ */
461
771
  async intelligentContentCreation(description, additionalData) {
462
- // First, try to find content types that match
772
+ // STEP 1: Advanced Intent Analysis with Disambiguation Logic
773
+ const intentAnalysis = this.analyzeUserIntent(description, additionalData);
774
+ // Handle workflow-specific disambiguation
775
+ if (intentAnalysis.isWorkflowRelated) {
776
+ return await this.handleWorkflowDisambiguation(description, additionalData, intentAnalysis);
777
+ }
778
+ // STEP 2: Standard content type matching
463
779
  const contentTypeMatches = this.findContentTypesByDescription(description);
464
780
  if (contentTypeMatches.length === 0) {
465
- // If we can't find any matches, provide helpful suggestions
466
- const availableTypes = Object.entries(this.glossary).map(([key, type]) => {
467
- let title = 'Unknown';
468
- let plural = 'Unknown';
469
- if (type && typeof type === 'object') {
470
- if (type.definition) {
471
- title = type.definition.title || title;
472
- plural = type.definition.plural || plural;
473
- }
474
- else if (type.type) {
475
- title = type.type.title || title;
476
- plural = type.type.plural || plural;
477
- }
478
- else if (type.title) {
479
- title = type.title || title;
480
- plural = type.plural || plural;
481
- }
482
- }
483
- return { key, title, plural };
484
- });
781
+ return await this.handleNoContentTypeMatches(description);
782
+ }
783
+ if (contentTypeMatches.length > 1) {
784
+ return await this.handleMultipleContentTypeMatches(description, contentTypeMatches);
785
+ }
786
+ // STEP 3: Single match found - provide comprehensive guidance
787
+ const contentType = contentTypeMatches[0];
788
+ return await this.handleSingleContentTypeMatch(contentType, description, additionalData);
789
+ }
790
+ /**
791
+ * Analyzes user intent to distinguish between different types of content creation
792
+ */
793
+ analyzeUserIntent(description, additionalData) {
794
+ const normalizedDesc = description.toLowerCase().trim();
795
+ const contextClues = [];
796
+ // Workflow-related keywords
797
+ const workflowKeywords = ['workflow', 'kanban', 'board', 'column', 'step', 'process', 'pipeline'];
798
+ const isWorkflowRelated = workflowKeywords.some(keyword => normalizedDesc.includes(keyword));
799
+ // Definition creation indicators
800
+ const definitionIndicators = [
801
+ 'create a new', 'create new', 'make a new', 'design a', 'set up a', 'build a',
802
+ 'define a', 'establish a', 'configure a'
803
+ ];
804
+ const isDefinitionCreation = definitionIndicators.some(indicator => normalizedDesc.includes(indicator));
805
+ // Instance creation indicators
806
+ const instanceIndicators = [
807
+ 'add', 'assign', 'put', 'move', 'place', 'insert', 'include'
808
+ ];
809
+ const isInstanceCreation = instanceIndicators.some(indicator => normalizedDesc.includes(indicator));
810
+ // Person assignment indicators
811
+ const personIndicators = [
812
+ 'add person', 'assign person', 'add user', 'assign user', 'add someone', 'assign someone',
813
+ 'add jim', 'add john', 'add sarah', 'put person', 'move person'
814
+ ];
815
+ const isPersonAssignment = personIndicators.some(indicator => normalizedDesc.includes(indicator));
816
+ // Collect context clues
817
+ if (isWorkflowRelated)
818
+ contextClues.push('workflow-related');
819
+ if (isDefinitionCreation)
820
+ contextClues.push('definition-creation');
821
+ if (isInstanceCreation)
822
+ contextClues.push('instance-creation');
823
+ if (isPersonAssignment)
824
+ contextClues.push('person-assignment');
825
+ // Calculate confidence based on clarity of intent
826
+ let confidence = 0.5; // Base confidence
827
+ if (isDefinitionCreation && !isInstanceCreation)
828
+ confidence = 0.9;
829
+ if (isInstanceCreation && !isDefinitionCreation)
830
+ confidence = 0.9;
831
+ if (isPersonAssignment)
832
+ confidence = 0.95;
833
+ return {
834
+ isWorkflowRelated,
835
+ isDefinitionCreation,
836
+ isInstanceCreation,
837
+ isPersonAssignment,
838
+ confidence,
839
+ contextClues
840
+ };
841
+ }
842
+ /**
843
+ * Handles workflow-specific disambiguation with comprehensive guidance
844
+ */
845
+ async handleWorkflowDisambiguation(description, additionalData, intentAnalysis) {
846
+ const normalizedDesc = description.toLowerCase().trim();
847
+ // Check if user wants to create a workflow DEFINITION
848
+ if (intentAnalysis.isDefinitionCreation ||
849
+ normalizedDesc.includes('create a workflow') ||
850
+ normalizedDesc.includes('new workflow') ||
851
+ normalizedDesc.includes('design workflow')) {
485
852
  return {
486
853
  content: [{
487
854
  type: 'text',
488
- text: `I couldn't find a content type for "${description}". Here are the available content types:\n\n${availableTypes.map(t => `- ${t.key}: ${t.title} (${t.plural})`).join('\n')}\n\nPlease specify which content type you'd like to create, or I can help you create content using one of these types.`,
855
+ text: `🔧 **WORKFLOW DEFINITION CREATION**
856
+
857
+ You want to create a new workflow definition (template). This defines the structure, columns, steps, and automation rules.
858
+
859
+ **WORKFLOW DEFINITION STRUCTURE:**
860
+
861
+ A workflow definition includes:
862
+ - **Columns**: Stages like "To Do", "In Progress", "Review", "Done"
863
+ - **Steps**: Specific positions within columns where cards can be placed
864
+ - **Automation**: Functions that run when cards enter/exit steps
865
+ - **Due Date Behavior**: How due dates are calculated and managed
866
+ - **Completion Criteria**: Rules that determine when workflow is complete
867
+
868
+ **EXAMPLE WORKFLOW DEFINITION:**
869
+ \`\`\`json
870
+ {
871
+ "type": "definition",
872
+ "title": "New Student Induction Workflow",
873
+ "definesType": "workflowcard",
874
+ "workflow": [
875
+ {
876
+ "title": "Enrollment",
877
+ "description": "Initial enrollment and documentation",
878
+ "steps": [
879
+ {
880
+ "title": "Application Received",
881
+ "type": "step",
882
+ "description": "Student application has been received",
883
+ "duration": 1440,
884
+ "assignees": [],
885
+ "entryFunction": "// Code to run when card enters this step",
886
+ "exitFunction": "// Code to run when card exits this step"
887
+ }
888
+ ]
889
+ },
890
+ {
891
+ "title": "Processing",
892
+ "description": "Review and approval process",
893
+ "steps": [
894
+ {
895
+ "title": "Document Review",
896
+ "type": "step",
897
+ "description": "Review all submitted documents"
898
+ }
899
+ ]
900
+ }
901
+ ]
902
+ }
903
+ \`\`\`
904
+
905
+ To create this workflow definition, use:
906
+ \`qik_create_content\` with type: "definition" and the workflow structure in the data field.
907
+
908
+ Would you like me to help you create a specific workflow definition?`,
489
909
  }],
490
- isError: true,
491
910
  };
492
911
  }
493
- if (contentTypeMatches.length > 1) {
494
- // Multiple matches found - ask for clarification
495
- const matchDetails = contentTypeMatches.map(key => {
496
- const type = this.glossary[key];
497
- let title = 'Unknown';
498
- let plural = 'Unknown';
499
- if (type && typeof type === 'object') {
500
- if (type.definition) {
501
- title = type.definition.title || title;
502
- plural = type.definition.plural || plural;
503
- }
504
- else if (type.type) {
505
- title = type.type.title || title;
506
- plural = type.type.plural || plural;
507
- }
508
- else if (type.title) {
509
- title = type.title || title;
510
- plural = type.plural || plural;
511
- }
512
- }
513
- return { key, title, plural };
514
- });
912
+ // Check if user wants to add someone to an existing workflow (create workflow CARD)
913
+ if (intentAnalysis.isPersonAssignment ||
914
+ normalizedDesc.includes('add') && normalizedDesc.includes('to workflow') ||
915
+ normalizedDesc.includes('assign') && normalizedDesc.includes('workflow')) {
515
916
  return {
516
917
  content: [{
517
918
  type: 'text',
518
- text: `I found multiple content types that match "${description}". Please clarify which one you'd like to create:\n\n${matchDetails.map(t => `- ${t.key}: ${t.title} (${t.plural})`).join('\n')}\n\nPlease specify the exact content type key you'd like to use.`,
919
+ text: `👤 **WORKFLOW CARD CREATION (Person Assignment)**
920
+
921
+ You want to add a person to an existing workflow by creating a workflow card instance.
922
+
923
+ **WORKFLOW CARD vs WORKFLOW DEFINITION:**
924
+ - **Workflow Definition**: The template/structure (columns, steps, rules)
925
+ - **Workflow Card**: Individual items that move through the workflow
926
+
927
+ **TO ADD SOMEONE TO A WORKFLOW:**
928
+
929
+ 1. **Find the workflow definition ID** first using:
930
+ \`qik_list_content\` with type: "definition" and search for your workflow name
931
+
932
+ 2. **Create a workflow card** using:
933
+ \`qik_create_content\` with type: "workflowcard"
934
+
935
+ **EXAMPLE WORKFLOW CARD:**
936
+ \`\`\`json
937
+ {
938
+ "type": "workflowcard",
939
+ "title": "John Smith - Student Induction",
940
+ "reference": "PROFILE_ID_HERE",
941
+ "referenceType": "profile",
942
+ "data": {
943
+ "workflowDefinition": "WORKFLOW_DEFINITION_ID_HERE",
944
+ "currentStep": "application-received",
945
+ "assignedTo": ["USER_ID_HERE"],
946
+ "dueDate": "2024-01-15T09:00:00.000Z"
947
+ }
948
+ }
949
+ \`\`\`
950
+
951
+ **NEED MORE HELP?**
952
+ - What's the name of the workflow you want to add someone to?
953
+ - Who do you want to add to the workflow?
954
+ - Do you have the workflow definition ID?`,
519
955
  }],
520
- isError: true,
521
956
  };
522
957
  }
523
- // Single match found
524
- const contentType = contentTypeMatches[0];
958
+ // General workflow guidance
959
+ return {
960
+ content: [{
961
+ type: 'text',
962
+ text: `🔄 **WORKFLOW SYSTEM GUIDANCE**
963
+
964
+ I detected you're working with workflows. Please clarify your intent:
965
+
966
+ **OPTION 1: Create Workflow Definition (Template)**
967
+ - "Create a new workflow"
968
+ - "Design a student onboarding workflow"
969
+ - "Set up a project management workflow"
970
+ → Creates the structure, columns, steps, and rules
971
+
972
+ **OPTION 2: Add Person to Existing Workflow**
973
+ - "Add Jim to the student workflow"
974
+ - "Assign Sarah to project workflow"
975
+ - "Put John in the onboarding process"
976
+ → Creates a workflow card instance for a person
977
+
978
+ **WORKFLOW CONCEPTS:**
979
+ - **Definition**: The template (like a Kanban board layout)
980
+ - **Card**: Individual items moving through the workflow
981
+ - **Columns**: Stages (To Do, In Progress, Done)
982
+ - **Steps**: Specific positions within columns
983
+ - **Automation**: Code that runs when cards move
984
+
985
+ **AVAILABLE WORKFLOW CONTENT TYPES:**
986
+ - \`definition\`: For creating workflow templates
987
+ - \`workflowcard\`: For individual workflow instances
988
+ - \`object\`: For custom workflow-related objects
989
+
990
+ Please specify: Are you creating a new workflow template, or adding someone to an existing workflow?`,
991
+ }],
992
+ };
993
+ }
994
+ /**
995
+ * Handles cases where no content types match the description
996
+ */
997
+ async handleNoContentTypeMatches(description) {
998
+ const availableTypes = Object.entries(this.glossary).map(([key, type]) => {
999
+ let title = 'Unknown';
1000
+ let plural = 'Unknown';
1001
+ if (type && typeof type === 'object') {
1002
+ if (type.definition) {
1003
+ title = type.definition.title || title;
1004
+ plural = type.definition.plural || plural;
1005
+ }
1006
+ else if (type.type) {
1007
+ title = type.type.title || title;
1008
+ plural = type.type.plural || plural;
1009
+ }
1010
+ else if (type.title) {
1011
+ title = type.title || title;
1012
+ plural = type.plural || plural;
1013
+ }
1014
+ }
1015
+ return { key, title, plural };
1016
+ });
1017
+ // Group types by category for better organization
1018
+ const categorizedTypes = this.categorizeContentTypes(availableTypes);
1019
+ return {
1020
+ content: [{
1021
+ type: 'text',
1022
+ text: `❌ **NO MATCHING CONTENT TYPE FOUND**
1023
+
1024
+ I couldn't find a content type for "${description}".
1025
+
1026
+ **AVAILABLE CONTENT TYPES BY CATEGORY:**
1027
+
1028
+ ${categorizedTypes}
1029
+
1030
+ **SUGGESTIONS:**
1031
+ - Try using more specific terms (e.g., "incident report" instead of "report")
1032
+ - Check if you meant to create a workflow definition or workflow card
1033
+ - Use the exact content type key from the list above
1034
+
1035
+ **NEED HELP?**
1036
+ - Use \`qik_get_glossary\` to see all available types with descriptions
1037
+ - Use \`qik_get_content_definition\` with a specific type to see its fields`,
1038
+ }],
1039
+ isError: true,
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Categorizes content types for better organization in help text
1044
+ */
1045
+ categorizeContentTypes(types) {
1046
+ const categories = {
1047
+ 'Core Content': [],
1048
+ 'People & Profiles': [],
1049
+ 'Workflows & Processes': [],
1050
+ 'Communication': [],
1051
+ 'Media & Files': [],
1052
+ 'System & Admin': [],
1053
+ 'Other': []
1054
+ };
1055
+ for (const type of types) {
1056
+ const key = type.key.toLowerCase();
1057
+ const title = type.title.toLowerCase();
1058
+ if (key.includes('profile') || key.includes('person') || key.includes('user')) {
1059
+ categories['People & Profiles'].push(type);
1060
+ }
1061
+ else if (key.includes('workflow') || key.includes('definition') || key.includes('process')) {
1062
+ categories['Workflows & Processes'].push(type);
1063
+ }
1064
+ else if (key.includes('comment') || key.includes('message') || key.includes('notification') || key.includes('email')) {
1065
+ categories['Communication'].push(type);
1066
+ }
1067
+ else if (key.includes('file') || key.includes('image') || key.includes('video') || key.includes('audio')) {
1068
+ categories['Media & Files'].push(type);
1069
+ }
1070
+ else if (key.includes('scope') || key.includes('role') || key.includes('policy') || key.includes('variable')) {
1071
+ categories['System & Admin'].push(type);
1072
+ }
1073
+ else if (['article', 'event', 'object'].includes(key)) {
1074
+ categories['Core Content'].push(type);
1075
+ }
1076
+ else {
1077
+ categories['Other'].push(type);
1078
+ }
1079
+ }
1080
+ let result = '';
1081
+ for (const [category, categoryTypes] of Object.entries(categories)) {
1082
+ if (categoryTypes.length > 0) {
1083
+ result += `\n**${category}:**\n`;
1084
+ result += categoryTypes.map(t => `- ${t.key}: ${t.title} (${t.plural})`).join('\n');
1085
+ result += '\n';
1086
+ }
1087
+ }
1088
+ return result;
1089
+ }
1090
+ /**
1091
+ * Handles cases where multiple content types match
1092
+ */
1093
+ async handleMultipleContentTypeMatches(description, contentTypeMatches) {
1094
+ const matchDetails = contentTypeMatches.map(key => {
1095
+ const type = this.glossary[key];
1096
+ let title = 'Unknown';
1097
+ let plural = 'Unknown';
1098
+ let baseType = '';
1099
+ if (type && typeof type === 'object') {
1100
+ if (type.definition) {
1101
+ title = type.definition.title || title;
1102
+ plural = type.definition.plural || plural;
1103
+ baseType = type.definition.definesType || '';
1104
+ }
1105
+ else if (type.type) {
1106
+ title = type.type.title || title;
1107
+ plural = type.type.plural || plural;
1108
+ }
1109
+ else if (type.title) {
1110
+ title = type.title || title;
1111
+ plural = type.plural || plural;
1112
+ baseType = type.definesType || '';
1113
+ }
1114
+ }
1115
+ return { key, title, plural, baseType };
1116
+ });
1117
+ return {
1118
+ content: [{
1119
+ type: 'text',
1120
+ text: `🔍 **MULTIPLE CONTENT TYPES FOUND**
1121
+
1122
+ I found multiple content types that match "${description}". Please clarify which one you'd like to create:
1123
+
1124
+ ${matchDetails.map(t => {
1125
+ let description = `- **${t.key}**: ${t.title} (${t.plural})`;
1126
+ if (t.baseType) {
1127
+ description += ` - extends ${t.baseType}`;
1128
+ }
1129
+ return description;
1130
+ }).join('\n')}
1131
+
1132
+ **TO PROCEED:**
1133
+ 1. Choose the exact content type key from above
1134
+ 2. Use \`qik_create_content\` with your chosen type
1135
+ 3. Or use \`qik_get_content_definition\` to see field details first
1136
+
1137
+ **NEED MORE INFO?**
1138
+ Use \`qik_get_content_definition\` with any of the type keys above to see their specific fields and requirements.`,
1139
+ }],
1140
+ isError: true,
1141
+ };
1142
+ }
1143
+ /**
1144
+ * Handles single content type match with comprehensive guidance
1145
+ */
1146
+ async handleSingleContentTypeMatch(contentType, description, additionalData) {
525
1147
  const typeInfo = this.getContentTypeInfo(contentType);
526
1148
  if (!typeInfo) {
527
1149
  return {
528
1150
  content: [{
529
1151
  type: 'text',
530
- text: `Found content type "${contentType}" but couldn't load its definition.`,
1152
+ text: `❌ Found content type "${contentType}" but couldn't load its definition.`,
531
1153
  }],
532
1154
  isError: true,
533
1155
  };
534
1156
  }
535
- // Analyze the fields to provide guidance
1157
+ // Extract comprehensive type information
1158
+ const typeAnalysis = this.analyzeContentTypeStructure(typeInfo);
1159
+ let guidance = `✅ **CONTENT TYPE FOUND: ${contentType.toUpperCase()}**\n\n`;
1160
+ guidance += `**Type**: ${typeAnalysis.title}\n`;
1161
+ guidance += `**Description**: ${typeAnalysis.description || 'No description available'}\n`;
1162
+ if (typeAnalysis.baseType) {
1163
+ guidance += `**Extends**: ${typeAnalysis.baseType}\n`;
1164
+ }
1165
+ guidance += `\n**FIELD STRUCTURE:**\n`;
1166
+ if (typeAnalysis.requiredFields.length > 0) {
1167
+ guidance += `\n**Required Fields:**\n`;
1168
+ guidance += typeAnalysis.requiredFields.map(f => `- **${f.key}** (${f.title}): ${f.description || 'No description'}`).join('\n');
1169
+ }
1170
+ if (typeAnalysis.optionalFields.length > 0) {
1171
+ guidance += `\n\n**Optional Fields:**\n`;
1172
+ guidance += typeAnalysis.optionalFields.map(f => `- **${f.key}** (${f.title}): ${f.description || 'No description'}`).join('\n');
1173
+ }
1174
+ // Handle creation if data provided
1175
+ if (additionalData && typeof additionalData === 'object' && additionalData.title) {
1176
+ return await this.handleContentCreationWithData(contentType, additionalData, typeAnalysis);
1177
+ }
1178
+ // Provide creation guidance
1179
+ guidance += await this.generateCreationGuidance(contentType, typeAnalysis);
1180
+ return {
1181
+ content: [{
1182
+ type: 'text',
1183
+ text: guidance,
1184
+ }],
1185
+ };
1186
+ }
1187
+ /**
1188
+ * Analyzes content type structure for comprehensive information
1189
+ */
1190
+ analyzeContentTypeStructure(typeInfo) {
536
1191
  let fields = [];
537
- let typeTitle = 'Unknown';
1192
+ let title = 'Unknown';
1193
+ let description = '';
1194
+ let baseType = '';
538
1195
  if (typeInfo.definition) {
539
1196
  fields = typeInfo.definition.fields || [];
540
- typeTitle = typeInfo.definition.title || typeTitle;
1197
+ title = typeInfo.definition.title || title;
1198
+ description = typeInfo.definition.description || '';
1199
+ baseType = typeInfo.definition.definesType || '';
541
1200
  }
542
1201
  else if (typeInfo.type) {
543
1202
  fields = typeInfo.type.fields || [];
544
- typeTitle = typeInfo.type.title || typeTitle;
1203
+ title = typeInfo.type.title || title;
1204
+ description = typeInfo.type.description || '';
545
1205
  }
546
1206
  else if (typeInfo.fields) {
547
1207
  fields = typeInfo.fields || [];
548
- typeTitle = typeInfo.title || typeTitle;
1208
+ title = typeInfo.title || title;
1209
+ description = typeInfo.description || '';
1210
+ baseType = typeInfo.definesType || '';
549
1211
  }
550
1212
  const requiredFields = fields.filter((f) => f.minimum && f.minimum > 0);
551
1213
  const optionalFields = fields.filter((f) => !f.minimum || f.minimum === 0);
552
- let guidance = `I found the content type "${contentType}" (${typeTitle}) for "${description}".\n\n`;
553
- if (requiredFields.length > 0) {
554
- guidance += `Required fields:\n${requiredFields.map(f => `- ${f.key} (${f.title}): ${f.description || 'No description'}`).join('\n')}\n\n`;
555
- }
556
- if (optionalFields.length > 0) {
557
- guidance += `Optional fields:\n${optionalFields.map(f => `- ${f.key} (${f.title}): ${f.description || 'No description'}`).join('\n')}\n\n`;
558
- }
559
- // If additional data was provided, check if we have scopes and try to create the content
560
- if (additionalData && typeof additionalData === 'object' && additionalData.title) {
561
- // Check if scopes are provided
562
- if (!additionalData.meta || !additionalData.meta.scopes || !Array.isArray(additionalData.meta.scopes) || additionalData.meta.scopes.length === 0) {
563
- // Get available scopes
564
- const scopeTree = await this.getAvailableScopes();
565
- if (scopeTree) {
566
- const availableScopes = this.extractScopesWithPermissions(scopeTree, 'create');
567
- if (availableScopes.length === 0) {
568
- return {
569
- content: [{
570
- type: 'text',
571
- text: `You don't have permission to create content in any scopes. Please contact your administrator.`,
572
- }],
573
- isError: true,
574
- };
575
- }
1214
+ return {
1215
+ title,
1216
+ description,
1217
+ baseType,
1218
+ fields,
1219
+ requiredFields,
1220
+ optionalFields,
1221
+ fieldCount: fields.length
1222
+ };
1223
+ }
1224
+ /**
1225
+ * Handles content creation when data is provided
1226
+ */
1227
+ async handleContentCreationWithData(contentType, additionalData, typeAnalysis) {
1228
+ // Check if scopes are provided
1229
+ if (!additionalData.meta || !additionalData.meta.scopes || !Array.isArray(additionalData.meta.scopes) || additionalData.meta.scopes.length === 0) {
1230
+ const scopeTree = await this.getAvailableScopes();
1231
+ if (scopeTree) {
1232
+ const availableScopes = this.extractScopesWithPermissions(scopeTree, 'create');
1233
+ if (availableScopes.length === 0) {
576
1234
  return {
577
1235
  content: [{
578
1236
  type: 'text',
579
- text: `To create "${additionalData.title}" as a ${typeTitle}, you need to specify which scope to create it in. You have permission to create content in these scopes:\n\n${availableScopes.map(s => `- ${s.id}: ${s.path}`).join('\n')}\n\nPlease use the qik_create_content tool with:\n- type: "${contentType}"\n- title: "${additionalData.title}"\n- meta: { "scopes": ["scope_id_here"] }\n- data: { /* your field values */ }`,
1237
+ text: `🚫 **PERMISSION DENIED**\n\nYou don't have permission to create content in any scopes. Please contact your administrator.`,
580
1238
  }],
1239
+ isError: true,
581
1240
  };
582
1241
  }
1242
+ return {
1243
+ content: [{
1244
+ type: 'text',
1245
+ 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 */ }`,
1246
+ }],
1247
+ };
583
1248
  }
584
- // Scopes are provided, proceed with creation
585
- const title = additionalData.title || `New ${typeTitle}`;
586
- const data = additionalData.data || {};
587
- const meta = additionalData.meta || {};
588
- return await this.createContent({
589
- type: contentType,
590
- title,
591
- data,
592
- meta,
593
- });
594
1249
  }
595
- // Get available scopes for guidance
1250
+ // Proceed with creation
1251
+ const title = additionalData.title || `New ${typeAnalysis.title}`;
1252
+ const data = additionalData.data || {};
1253
+ const meta = additionalData.meta || {};
1254
+ return await this.createContent({
1255
+ type: contentType,
1256
+ title,
1257
+ data,
1258
+ meta,
1259
+ });
1260
+ }
1261
+ /**
1262
+ * Generates comprehensive creation guidance
1263
+ */
1264
+ async generateCreationGuidance(contentType, typeAnalysis) {
1265
+ let guidance = `\n\n**CREATION GUIDANCE:**\n`;
1266
+ // Get available scopes
596
1267
  const scopeTree = await this.getAvailableScopes();
597
1268
  let scopeGuidance = '';
598
1269
  if (scopeTree) {
599
1270
  const availableScopes = this.extractScopesWithPermissions(scopeTree, 'create');
600
1271
  if (availableScopes.length > 0) {
601
- scopeGuidance = `\n\nAvailable scopes you can create content in:\n${availableScopes.map(s => `- ${s.id}: ${s.path}`).join('\n')}\n`;
1272
+ scopeGuidance = `\n**Available Scopes:**\n${availableScopes.map(s => `- **${s.id}**: ${s.path}`).join('\n')}\n`;
602
1273
  }
603
1274
  }
604
- guidance += `To create this content, use the qik_create_content tool with:\n`;
605
- guidance += `- type: "${contentType}"\n`;
606
- guidance += `- title: "Your title here"\n`;
607
- guidance += `- meta: { "scopes": ["scope_id_here"] } (required)\n`;
608
- guidance += `- data: { /* field values */ }\n`;
1275
+ guidance += `\n**TO CREATE THIS CONTENT:**\n`;
1276
+ guidance += `Use \`qik_create_content\` with:\n`;
1277
+ guidance += `- **type**: "${contentType}"\n`;
1278
+ guidance += `- **title**: "Your title here"\n`;
1279
+ guidance += `- **meta**: { "scopes": ["scope_id_here"] } *(required)*\n`;
1280
+ guidance += `- **data**: { /* field values based on structure above */ }\n`;
609
1281
  guidance += scopeGuidance;
610
- return {
611
- content: [{
612
- type: 'text',
613
- text: guidance,
614
- }],
615
- };
1282
+ // Add specific guidance for common types
1283
+ if (contentType === 'definition') {
1284
+ guidance += `\n**WORKFLOW DEFINITION EXAMPLE:**\n`;
1285
+ guidance += `For workflow definitions, include the workflow structure in the data field with columns, steps, and automation rules.\n`;
1286
+ }
1287
+ if (contentType.includes('comment') || contentType.includes('Comment')) {
1288
+ guidance += `\n**COMMENT CREATION:**\n`;
1289
+ guidance += `Comments require a reference to the item being commented on. Include:\n`;
1290
+ guidance += `- **reference**: ID of the item to comment on\n`;
1291
+ guidance += `- **referenceType**: Type of the referenced item\n`;
1292
+ }
1293
+ return guidance;
1294
+ }
1295
+ generateToolDescription(baseDescription, emoji = '') {
1296
+ const prefix = emoji ? `${emoji} ` : '';
1297
+ return `${prefix}${baseDescription.replace(/Qik/g, this.serverName)}`;
616
1298
  }
617
1299
  setupToolHandlers() {
618
1300
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -622,7 +1304,7 @@ export class QikMCPServer {
622
1304
  // Authentication & Session
623
1305
  {
624
1306
  name: 'qik_get_user_session',
625
- description: 'Get current user session information',
1307
+ description: this.generateToolDescription('Get current user session information', '👤'),
626
1308
  inputSchema: {
627
1309
  type: 'object',
628
1310
  properties: {},
@@ -631,7 +1313,7 @@ export class QikMCPServer {
631
1313
  // Content Type Discovery
632
1314
  {
633
1315
  name: 'qik_get_glossary',
634
- description: 'Get all available content types and their definitions',
1316
+ description: this.generateToolDescription('Get all available content types and their definitions', '📚'),
635
1317
  inputSchema: {
636
1318
  type: 'object',
637
1319
  properties: {},
@@ -639,7 +1321,7 @@ export class QikMCPServer {
639
1321
  },
640
1322
  {
641
1323
  name: 'qik_get_content_definition',
642
- description: 'Get definition for a specific content type',
1324
+ description: this.generateToolDescription('Get definition for a specific content type', '🔍'),
643
1325
  inputSchema: {
644
1326
  type: 'object',
645
1327
  properties: {
@@ -655,7 +1337,7 @@ export class QikMCPServer {
655
1337
  // Content Management
656
1338
  {
657
1339
  name: 'qik_get_content',
658
- description: 'Get content item by ID or slug',
1340
+ description: this.generateToolDescription('Get content item by ID or slug', '📄'),
659
1341
  inputSchema: {
660
1342
  type: 'object',
661
1343
  properties: {
@@ -676,7 +1358,27 @@ export class QikMCPServer {
676
1358
  },
677
1359
  {
678
1360
  name: 'qik_list_content',
679
- description: 'List content items with filtering and search',
1361
+ description: this.generateToolDescription(`List content items with advanced filtering and search capabilities. Supports complex queries like birthdays, date ranges, and sophisticated business logic.
1362
+
1363
+ **ENHANCED FILTER CAPABILITIES:**
1364
+ - 40+ comparators for dates, strings, numbers, and arrays
1365
+ - Hierarchical filters with 'and', 'or', 'nor' operators
1366
+ - Anniversary and birthday queries (anniversarynext, anniversarypast)
1367
+ - Date range filtering (datebetween, datepast, datenext)
1368
+ - String matching (contains, startswith, endswith, equal)
1369
+ - Numeric comparisons (greater, lesser, between)
1370
+ - Array operations (in, notin, valuesgreater)
1371
+
1372
+ **COMMON USE CASES:**
1373
+ - Find birthdays in next 10 days: {"operator":"and","filters":[{"key":"dob","comparator":"anniversarynext","value":10,"value2":"days"}]}
1374
+ - Recent content: {"operator":"and","filters":[{"key":"meta.created","comparator":"datepast","value":30,"value2":"days"}]}
1375
+ - Gender filtering: {"operator":"and","filters":[{"key":"gender","comparator":"equal","value":"male"}]}
1376
+ - Complex queries with OR logic for multiple conditions
1377
+
1378
+ **FIELD TARGETING:**
1379
+ - Use dot notation for nested fields: "meta.created", "data.customField"
1380
+ - Target specific profile fields: "firstName", "lastName", "emails"
1381
+ - Filter by metadata: "meta.scopes", "meta.tags", "meta.security"`, '📋'),
680
1382
  inputSchema: {
681
1383
  type: 'object',
682
1384
  properties: {
@@ -687,31 +1389,50 @@ export class QikMCPServer {
687
1389
  },
688
1390
  search: {
689
1391
  type: 'string',
690
- description: 'Search keywords',
691
- },
692
- filter: {
693
- type: 'object',
694
- description: 'Filter criteria using Qik filter syntax',
1392
+ description: 'Search keywords - searches within title, tags, and text areas',
695
1393
  },
1394
+ filter: this.generateEnhancedFilterSchema(),
696
1395
  sort: {
697
1396
  type: 'object',
1397
+ description: 'Sorting configuration for results',
698
1398
  properties: {
699
- key: { type: 'string' },
700
- direction: { type: 'string', enum: ['asc', 'desc'] },
701
- type: { type: 'string', enum: ['string', 'number', 'date'] },
1399
+ key: {
1400
+ type: 'string',
1401
+ description: 'Field to sort by (e.g., "title", "meta.created", "data.customField")'
1402
+ },
1403
+ direction: {
1404
+ type: 'string',
1405
+ enum: ['asc', 'desc'],
1406
+ description: 'Sort direction: ascending or descending'
1407
+ },
1408
+ type: {
1409
+ type: 'string',
1410
+ enum: ['string', 'number', 'date'],
1411
+ description: 'Data type for proper sorting behavior'
1412
+ },
702
1413
  },
703
1414
  },
704
1415
  page: {
705
1416
  type: 'object',
1417
+ description: 'Pagination settings',
706
1418
  properties: {
707
- size: { type: 'number', minimum: 1, maximum: 100 },
708
- index: { type: 'number', minimum: 1 },
1419
+ size: {
1420
+ type: 'number',
1421
+ minimum: 1,
1422
+ maximum: 100,
1423
+ description: 'Number of items per page (1-100)'
1424
+ },
1425
+ index: {
1426
+ type: 'number',
1427
+ minimum: 1,
1428
+ description: 'Page number to retrieve (starts at 1)'
1429
+ },
709
1430
  },
710
1431
  },
711
1432
  select: {
712
1433
  type: 'array',
713
1434
  items: { type: 'string' },
714
- description: 'Fields to include in response',
1435
+ description: 'Specific fields to include in response (e.g., ["title", "data.make", "meta.created"])',
715
1436
  },
716
1437
  },
717
1438
  required: ['type'],
@@ -719,39 +1440,67 @@ export class QikMCPServer {
719
1440
  },
720
1441
  {
721
1442
  name: 'qik_create_content',
722
- description: 'Create new content item. IMPORTANT: Fields are structured based on content type - some go at root level, others in data object.',
1443
+ description: this.generateToolDescription(`Create new content item with intelligent field structure handling.
1444
+
1445
+ **FIELD STRUCTURE INTELLIGENCE:**
1446
+ - Automatically separates root-level fields from data object fields
1447
+ - Handles comment inheritance (comments inherit scopes from referenced items)
1448
+ - Validates field requirements based on content type definitions
1449
+ - Supports workflow definitions, workflow cards, and all content types
1450
+
1451
+ **FIELD PLACEMENT RULES:**
1452
+ - **Root Level**: reference, referenceType, body, organisation, title, meta
1453
+ - **Data Object**: Custom fields defined in content type definitions (definedFields)
1454
+ - **Meta Object**: scopes (required), tags, security, personaAuthor, etc.
1455
+
1456
+ **CONTENT TYPE EXAMPLES:**
1457
+ - **Comments**: Require reference + referenceType, inherit scopes automatically
1458
+ - **Workflow Definitions**: Use data object for workflow structure (columns, steps, automation)
1459
+ - **Workflow Cards**: Reference profiles, link to workflow definitions
1460
+ - **Profiles**: firstName, lastName at root, custom fields in data object
1461
+ - **Articles**: body at root level, custom article fields in data object
1462
+
1463
+ **SCOPE INHERITANCE:**
1464
+ - Comments automatically inherit scopes from referenced items
1465
+ - Other content types require explicit scope assignment
1466
+ - Use qik_get_scopes to find available scopes with permissions
1467
+
1468
+ **VALIDATION:**
1469
+ - Checks content type exists and user has access
1470
+ - Validates required fields based on content type definition
1471
+ - Ensures proper field placement (root vs data object)`, '✨'),
723
1472
  inputSchema: {
724
1473
  type: 'object',
725
1474
  properties: {
726
1475
  type: {
727
1476
  type: 'string',
728
- description: 'Content type to create',
1477
+ description: 'Content type to create (use qik_get_glossary to see all available types)',
729
1478
  enum: Object.keys(this.glossary),
730
1479
  },
731
1480
  title: {
732
1481
  type: 'string',
733
- description: 'Content title',
1482
+ description: 'Content title (required for all content types)',
734
1483
  },
735
1484
  // Generate dynamic properties based on content types
736
1485
  ...this.generateDynamicContentProperties(),
737
1486
  meta: {
738
1487
  type: 'object',
739
- description: 'Meta information (scopes, tags, etc.)',
1488
+ description: 'Meta information (scopes required for most content types)',
740
1489
  properties: {
741
1490
  scopes: {
742
1491
  type: 'array',
743
1492
  items: { type: 'string' },
744
- description: 'Scope IDs where this content should be stored',
1493
+ description: 'Scope IDs where this content should be stored (REQUIRED - use qik_get_scopes to find available)',
745
1494
  },
746
1495
  tags: {
747
1496
  type: 'array',
748
1497
  items: { type: 'string' },
749
- description: 'Tag IDs for this content',
1498
+ description: 'Tag IDs for categorization and search',
750
1499
  },
751
1500
  security: {
752
1501
  type: 'string',
753
1502
  enum: ['public', 'secure', 'private'],
754
- description: 'Security level for this content',
1503
+ description: 'Security level: public (everyone), secure (authenticated), private (restricted)',
755
1504
  },
756
1505
  },
757
1506
  },
@@ -761,7 +1510,7 @@ export class QikMCPServer {
761
1510
  },
762
1511
  {
763
1512
  name: 'qik_update_content',
764
- description: 'Update existing content item',
1513
+ description: this.generateToolDescription('Update existing content item', '✏️'),
765
1514
  inputSchema: {
766
1515
  type: 'object',
767
1516
  properties: {
@@ -784,7 +1533,7 @@ export class QikMCPServer {
784
1533
  },
785
1534
  {
786
1535
  name: 'qik_delete_content',
787
- description: 'Delete content item',
1536
+ description: this.generateToolDescription('Delete content item', '🗑️'),
788
1537
  inputSchema: {
789
1538
  type: 'object',
790
1539
  properties: {
@@ -799,7 +1548,7 @@ export class QikMCPServer {
799
1548
  // Profile Management
800
1549
  {
801
1550
  name: 'qik_list_profiles',
802
- description: 'Search and list profiles/people',
1551
+ description: this.generateToolDescription('Search and list profiles/people', '👥'),
803
1552
  inputSchema: {
804
1553
  type: 'object',
805
1554
  properties: {
@@ -823,7 +1572,7 @@ export class QikMCPServer {
823
1572
  },
824
1573
  {
825
1574
  name: 'qik_create_profile',
826
- description: 'Create new profile/person',
1575
+ description: this.generateToolDescription('Create new profile/person', '👤'),
827
1576
  inputSchema: {
828
1577
  type: 'object',
829
1578
  properties: {
@@ -867,7 +1616,7 @@ export class QikMCPServer {
867
1616
  // Form Management
868
1617
  {
869
1618
  name: 'qik_get_form',
870
- description: 'Get form definition',
1619
+ description: this.generateToolDescription('Get form definition', '📝'),
871
1620
  inputSchema: {
872
1621
  type: 'object',
873
1622
  properties: {
@@ -881,7 +1630,7 @@ export class QikMCPServer {
881
1630
  },
882
1631
  {
883
1632
  name: 'qik_submit_form',
884
- description: 'Submit form data',
1633
+ description: this.generateToolDescription('Submit form data', '📤'),
885
1634
  inputSchema: {
886
1635
  type: 'object',
887
1636
  properties: {
@@ -900,7 +1649,7 @@ export class QikMCPServer {
900
1649
  // File Management
901
1650
  {
902
1651
  name: 'qik_upload_file',
903
- description: 'Upload file to Qik',
1652
+ description: this.generateToolDescription('Upload file to platform', '📁'),
904
1653
  inputSchema: {
905
1654
  type: 'object',
906
1655
  properties: {
@@ -931,7 +1680,7 @@ export class QikMCPServer {
931
1680
  // Search & Discovery
932
1681
  {
933
1682
  name: 'qik_search_content',
934
- description: 'Global content search across all types',
1683
+ description: this.generateToolDescription('Global content search across all types', '🔎'),
935
1684
  inputSchema: {
936
1685
  type: 'object',
937
1686
  properties: {
@@ -959,7 +1708,7 @@ export class QikMCPServer {
959
1708
  },
960
1709
  {
961
1710
  name: 'qik_get_scopes',
962
- description: 'Get available scopes/permissions tree',
1711
+ description: this.generateToolDescription('Get available scopes/permissions tree', '🔐'),
963
1712
  inputSchema: {
964
1713
  type: 'object',
965
1714
  properties: {},
@@ -968,7 +1717,7 @@ export class QikMCPServer {
968
1717
  // Utility Tools
969
1718
  {
970
1719
  name: 'qik_get_smartlist',
971
- description: 'Execute a smartlist query',
1720
+ description: this.generateToolDescription('Execute a smartlist query', '📊'),
972
1721
  inputSchema: {
973
1722
  type: 'object',
974
1723
  properties: {
@@ -980,28 +1729,70 @@ export class QikMCPServer {
980
1729
  required: ['id'],
981
1730
  },
982
1731
  },
983
- // Intelligent Content Creation
1732
+ // Intelligent Content Creation with Advanced Disambiguation
984
1733
  {
985
1734
  name: 'qik_create_content_intelligent',
986
- description: 'Intelligently create content by description (e.g., "create an incident report", "make a new event")',
1735
+ description: this.generateToolDescription(`Intelligently create content with advanced disambiguation logic.
1736
+
1737
+ **WORKFLOW DISAMBIGUATION:**
1738
+ - "create a workflow" → Creates workflow DEFINITION (template)
1739
+ - "add Jim to workflow X" → Creates workflow CARD (instance)
1740
+ - "design student onboarding workflow" → Creates workflow DEFINITION
1741
+ - "assign Sarah to project workflow" → Creates workflow CARD
1742
+
1743
+ **CONTENT TYPE DISAMBIGUATION:**
1744
+ - Automatically detects intent between definitions vs instances
1745
+ - Provides comprehensive guidance for workflow systems
1746
+ - Categorizes content types for better organization
1747
+ - Handles scope permissions and requirements
1748
+
1749
+ **EXAMPLES:**
1750
+ - "create an incident report" → Finds incident report content type
1751
+ - "make a new workflow" → Guides through workflow definition creation
1752
+ - "add person to existing workflow" → Guides through workflow card creation
1753
+ - "design a student induction process" → Creates workflow definition
1754
+
1755
+ **WORKFLOW CONCEPTS EXPLAINED:**
1756
+ - **Workflow Definition**: Template with columns, steps, automation rules
1757
+ - **Workflow Card**: Individual items that move through the workflow
1758
+ - **Columns**: Stages like "To Do", "In Progress", "Done"
1759
+ - **Steps**: Specific positions within columns
1760
+ - **Automation**: Entry/exit/success/fail functions`, '🧠'),
987
1761
  inputSchema: {
988
1762
  type: 'object',
989
1763
  properties: {
990
1764
  description: {
991
1765
  type: 'string',
992
- description: 'Natural language description of what to create (e.g., "incident report", "event", "article")',
1766
+ 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"',
993
1767
  },
994
1768
  title: {
995
1769
  type: 'string',
996
- description: 'Title for the content',
1770
+ description: 'Title for the content (optional - will be requested if needed)',
997
1771
  },
998
1772
  data: {
999
1773
  type: 'object',
1000
- description: 'Content data fields',
1774
+ description: 'Content data fields (optional - will be guided through structure)',
1001
1775
  },
1002
1776
  meta: {
1003
1777
  type: 'object',
1004
- description: 'Meta information (scopes, tags, etc.)',
1778
+ description: 'Meta information including scopes, tags, security level (will be guided through requirements)',
1779
+ properties: {
1780
+ scopes: {
1781
+ type: 'array',
1782
+ items: { type: 'string' },
1783
+ description: 'Scope IDs where content should be created'
1784
+ },
1785
+ tags: {
1786
+ type: 'array',
1787
+ items: { type: 'string' },
1788
+ description: 'Tag IDs for categorization'
1789
+ },
1790
+ security: {
1791
+ type: 'string',
1792
+ enum: ['public', 'secure', 'private'],
1793
+ description: 'Security level for the content'
1794
+ }
1795
+ }
1005
1796
  },
1006
1797
  },
1007
1798
  required: ['description'],