@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.
- package/build/src/index.d.ts +86 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +935 -144
- package/build/src/index.js.map +1 -1
- package/package.json +1 -1
package/build/src/index.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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:
|
|
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 (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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:
|
|
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
|
-
//
|
|
524
|
-
|
|
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:
|
|
1152
|
+
text: `❌ Found content type "${contentType}" but couldn't load its definition.`,
|
|
531
1153
|
}],
|
|
532
1154
|
isError: true,
|
|
533
1155
|
};
|
|
534
1156
|
}
|
|
535
|
-
//
|
|
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
|
|
1192
|
+
let title = 'Unknown';
|
|
1193
|
+
let description = '';
|
|
1194
|
+
let baseType = '';
|
|
538
1195
|
if (typeInfo.definition) {
|
|
539
1196
|
fields = typeInfo.definition.fields || [];
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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:
|
|
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
|
-
//
|
|
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
|
|
1272
|
+
scopeGuidance = `\n**Available Scopes:**\n${availableScopes.map(s => `- **${s.id}**: ${s.path}`).join('\n')}\n`;
|
|
602
1273
|
}
|
|
603
1274
|
}
|
|
604
|
-
guidance +=
|
|
605
|
-
guidance +=
|
|
606
|
-
guidance += `-
|
|
607
|
-
guidance += `-
|
|
608
|
-
guidance += `-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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:
|
|
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: {
|
|
700
|
-
|
|
701
|
-
|
|
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: {
|
|
708
|
-
|
|
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: '
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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'],
|