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