@qikdev/mcp 1.0.0 → 2.0.1
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/README.md +175 -175
- package/build/src/index.d.ts +21 -10
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +544 -155
- package/build/src/index.js.map +1 -1
- package/package.json +2 -2
package/build/src/index.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Qik Platform MCP Server
|
|
3
|
+
* Qik Platform MCP Server - Enhanced Version
|
|
4
4
|
*
|
|
5
5
|
* This MCP server provides comprehensive integration with the Qik platform,
|
|
6
6
|
* enabling AI assistants to interact with Qik's content management system,
|
|
7
7
|
* user management, forms, files, and more.
|
|
8
8
|
*
|
|
9
|
-
* Features:
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* - Scope and permission management
|
|
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
|
|
17
16
|
*/
|
|
18
17
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -27,13 +26,16 @@ export class QikMCPServer {
|
|
|
27
26
|
server;
|
|
28
27
|
axiosInstance;
|
|
29
28
|
glossary = {};
|
|
29
|
+
userSession = null;
|
|
30
|
+
lastGlossaryUpdate = 0;
|
|
31
|
+
GLOSSARY_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
30
32
|
constructor() {
|
|
31
33
|
if (!QIK_ACCESS_TOKEN) {
|
|
32
34
|
throw new Error('QIK_ACCESS_TOKEN environment variable is required. Run "qik-mcp-server setup" to configure.');
|
|
33
35
|
}
|
|
34
36
|
this.server = new Server({
|
|
35
37
|
name: "qik-mcp-server",
|
|
36
|
-
version: "
|
|
38
|
+
version: "2.0.0",
|
|
37
39
|
}, {
|
|
38
40
|
capabilities: {
|
|
39
41
|
tools: {},
|
|
@@ -49,37 +51,198 @@ export class QikMCPServer {
|
|
|
49
51
|
timeout: 30000,
|
|
50
52
|
});
|
|
51
53
|
this.setupToolHandlers();
|
|
52
|
-
this.
|
|
54
|
+
this.initializeServer();
|
|
53
55
|
// Error handling
|
|
54
|
-
this.server.onerror = (error) =>
|
|
56
|
+
this.server.onerror = (error) => this.log(`MCP Error: ${error}`);
|
|
55
57
|
process.on('SIGINT', async () => {
|
|
56
58
|
await this.server.close();
|
|
57
59
|
process.exit(0);
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
log(message) {
|
|
63
|
+
// Only log in development or when explicitly enabled
|
|
64
|
+
if (process.env.NODE_ENV !== 'production' || process.env.QIK_MCP_DEBUG === 'true') {
|
|
65
|
+
// Use stderr to avoid interfering with MCP JSON protocol on stdout
|
|
66
|
+
process.stderr.write(`[Qik MCP] ${message}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async initializeServer() {
|
|
70
|
+
try {
|
|
71
|
+
// Load user session first
|
|
72
|
+
await this.loadUserSession();
|
|
73
|
+
// Then load glossary
|
|
74
|
+
await this.loadGlossary();
|
|
75
|
+
this.log(`Initialized with ${Object.keys(this.glossary).length} content types`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
this.log(`Failed to initialize server: ${this.formatError(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async loadUserSession() {
|
|
82
|
+
try {
|
|
83
|
+
const response = await this.axiosInstance.get('/user');
|
|
84
|
+
this.userSession = response.data.session || response.data;
|
|
85
|
+
this.log(`Authenticated as ${this.userSession?.firstName} ${this.userSession?.lastName}`);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
this.log(`Failed to load user session: ${this.formatError(error)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async loadGlossary(force = false) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
if (!force && this.lastGlossaryUpdate && (now - this.lastGlossaryUpdate) < this.GLOSSARY_CACHE_TTL) {
|
|
94
|
+
return; // Use cached version
|
|
95
|
+
}
|
|
61
96
|
try {
|
|
62
97
|
const response = await this.axiosInstance.get('/glossary');
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
const newGlossary = response.data || {};
|
|
99
|
+
// Validate glossary structure
|
|
100
|
+
if (typeof newGlossary === 'object' && newGlossary !== null) {
|
|
101
|
+
this.glossary = newGlossary;
|
|
102
|
+
this.lastGlossaryUpdate = now;
|
|
103
|
+
this.log(`Loaded ${Object.keys(this.glossary).length} content types from glossary`);
|
|
104
|
+
// Log available content types for debugging
|
|
105
|
+
const contentTypes = Object.keys(this.glossary).sort();
|
|
106
|
+
this.log(`Available content types: ${contentTypes.join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.log('Invalid glossary response structure - keeping existing glossary');
|
|
110
|
+
}
|
|
65
111
|
}
|
|
66
112
|
catch (error) {
|
|
67
|
-
|
|
113
|
+
this.log(`Failed to load glossary: ${this.formatError(error)}`);
|
|
114
|
+
// Don't clear existing glossary on error - keep what we have
|
|
115
|
+
if (Object.keys(this.glossary).length === 0) {
|
|
116
|
+
this.log('No cached glossary available - some operations may fail');
|
|
117
|
+
}
|
|
68
118
|
}
|
|
69
119
|
}
|
|
70
120
|
formatError(error) {
|
|
71
121
|
if (axios.isAxiosError(error)) {
|
|
72
122
|
const axiosError = error;
|
|
73
123
|
if (axiosError.response) {
|
|
74
|
-
|
|
124
|
+
const status = axiosError.response.status;
|
|
125
|
+
const data = axiosError.response.data;
|
|
126
|
+
return `HTTP ${status}: ${JSON.stringify(data)}`;
|
|
75
127
|
}
|
|
76
128
|
return `Network error: ${axiosError.message}`;
|
|
77
129
|
}
|
|
78
130
|
return error.message || String(error);
|
|
79
131
|
}
|
|
132
|
+
getContentTypeInfo(type) {
|
|
133
|
+
return this.glossary[type] || null;
|
|
134
|
+
}
|
|
135
|
+
validateContentType(type) {
|
|
136
|
+
const info = this.getContentTypeInfo(type);
|
|
137
|
+
if (!info) {
|
|
138
|
+
const availableTypes = Object.keys(this.glossary).join(', ');
|
|
139
|
+
return {
|
|
140
|
+
valid: false,
|
|
141
|
+
error: `Content type '${type}' not found. Available types: ${availableTypes}`
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return { valid: true, info };
|
|
145
|
+
}
|
|
146
|
+
validateFieldData(data, fields, contentType) {
|
|
147
|
+
const errors = [];
|
|
148
|
+
for (const field of fields) {
|
|
149
|
+
const value = data[field.key];
|
|
150
|
+
// Check required fields
|
|
151
|
+
if (field.minimum && field.minimum > 0 && (!value || (Array.isArray(value) && value.length === 0))) {
|
|
152
|
+
errors.push(`Field '${field.key}' (${field.title}) is required for ${contentType}`);
|
|
153
|
+
}
|
|
154
|
+
// Type validation
|
|
155
|
+
if (value !== undefined && value !== null) {
|
|
156
|
+
switch (field.type) {
|
|
157
|
+
case 'string':
|
|
158
|
+
if (typeof value !== 'string') {
|
|
159
|
+
errors.push(`Field '${field.key}' must be a string, got ${typeof value}`);
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'number':
|
|
163
|
+
case 'integer':
|
|
164
|
+
if (typeof value !== 'number') {
|
|
165
|
+
errors.push(`Field '${field.key}' must be a number, got ${typeof value}`);
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'boolean':
|
|
169
|
+
if (typeof value !== 'boolean') {
|
|
170
|
+
errors.push(`Field '${field.key}' must be a boolean, got ${typeof value}`);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case 'array':
|
|
174
|
+
if (!Array.isArray(value)) {
|
|
175
|
+
errors.push(`Field '${field.key}' must be an array, got ${typeof value}`);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
case 'reference':
|
|
179
|
+
if (field.referenceType && typeof value === 'string') {
|
|
180
|
+
// Basic validation - could be enhanced to check if referenced item exists
|
|
181
|
+
}
|
|
182
|
+
else if (Array.isArray(value) && field.maximum !== 1) {
|
|
183
|
+
// Array of references
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
errors.push(`Field '${field.key}' must be a valid reference to ${field.referenceType || 'content'}`);
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { valid: errors.length === 0, errors };
|
|
193
|
+
}
|
|
194
|
+
generateFieldSchema(field) {
|
|
195
|
+
const schema = {
|
|
196
|
+
type: this.mapFieldTypeToJsonSchema(field.type),
|
|
197
|
+
description: field.description || field.title,
|
|
198
|
+
};
|
|
199
|
+
if (field.options && field.options.length > 0) {
|
|
200
|
+
schema.enum = field.options.map(opt => opt.value);
|
|
201
|
+
}
|
|
202
|
+
if (field.type === 'array' && field.fields) {
|
|
203
|
+
schema.items = {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: this.generateFieldsSchema(field.fields),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (field.type === 'reference') {
|
|
209
|
+
schema.description += field.referenceType ? ` (references ${field.referenceType})` : ' (content reference)';
|
|
210
|
+
}
|
|
211
|
+
return schema;
|
|
212
|
+
}
|
|
213
|
+
generateFieldsSchema(fields) {
|
|
214
|
+
const properties = {};
|
|
215
|
+
for (const field of fields) {
|
|
216
|
+
properties[field.key] = this.generateFieldSchema(field);
|
|
217
|
+
}
|
|
218
|
+
return properties;
|
|
219
|
+
}
|
|
220
|
+
mapFieldTypeToJsonSchema(fieldType) {
|
|
221
|
+
switch (fieldType) {
|
|
222
|
+
case 'string':
|
|
223
|
+
case 'email':
|
|
224
|
+
case 'url':
|
|
225
|
+
case 'date':
|
|
226
|
+
return 'string';
|
|
227
|
+
case 'number':
|
|
228
|
+
case 'integer':
|
|
229
|
+
return 'number';
|
|
230
|
+
case 'boolean':
|
|
231
|
+
return 'boolean';
|
|
232
|
+
case 'array':
|
|
233
|
+
return 'array';
|
|
234
|
+
case 'group':
|
|
235
|
+
case 'reference':
|
|
236
|
+
return 'object';
|
|
237
|
+
default:
|
|
238
|
+
return 'string';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
80
241
|
setupToolHandlers() {
|
|
81
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
82
|
-
|
|
242
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
243
|
+
// Ensure glossary is loaded
|
|
244
|
+
await this.loadGlossary();
|
|
245
|
+
const tools = [
|
|
83
246
|
// Authentication & Session
|
|
84
247
|
{
|
|
85
248
|
name: 'qik_get_user_session',
|
|
@@ -107,6 +270,7 @@ export class QikMCPServer {
|
|
|
107
270
|
type: {
|
|
108
271
|
type: 'string',
|
|
109
272
|
description: 'Content type key (e.g., "article", "profile", "event")',
|
|
273
|
+
enum: Object.keys(this.glossary),
|
|
110
274
|
},
|
|
111
275
|
},
|
|
112
276
|
required: ['type'],
|
|
@@ -142,7 +306,8 @@ export class QikMCPServer {
|
|
|
142
306
|
properties: {
|
|
143
307
|
type: {
|
|
144
308
|
type: 'string',
|
|
145
|
-
description: 'Content type to list
|
|
309
|
+
description: 'Content type to list',
|
|
310
|
+
enum: Object.keys(this.glossary),
|
|
146
311
|
},
|
|
147
312
|
search: {
|
|
148
313
|
type: 'string',
|
|
@@ -185,6 +350,7 @@ export class QikMCPServer {
|
|
|
185
350
|
type: {
|
|
186
351
|
type: 'string',
|
|
187
352
|
description: 'Content type to create',
|
|
353
|
+
enum: Object.keys(this.glossary),
|
|
188
354
|
},
|
|
189
355
|
title: {
|
|
190
356
|
type: 'string',
|
|
@@ -197,6 +363,23 @@ export class QikMCPServer {
|
|
|
197
363
|
meta: {
|
|
198
364
|
type: 'object',
|
|
199
365
|
description: 'Meta information (scopes, tags, etc.)',
|
|
366
|
+
properties: {
|
|
367
|
+
scopes: {
|
|
368
|
+
type: 'array',
|
|
369
|
+
items: { type: 'string' },
|
|
370
|
+
description: 'Scope IDs where this content should be stored',
|
|
371
|
+
},
|
|
372
|
+
tags: {
|
|
373
|
+
type: 'array',
|
|
374
|
+
items: { type: 'string' },
|
|
375
|
+
description: 'Tag IDs for this content',
|
|
376
|
+
},
|
|
377
|
+
security: {
|
|
378
|
+
type: 'string',
|
|
379
|
+
enum: ['public', 'secure', 'private'],
|
|
380
|
+
description: 'Security level for this content',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
200
383
|
},
|
|
201
384
|
},
|
|
202
385
|
required: ['type', 'title'],
|
|
@@ -384,7 +567,10 @@ export class QikMCPServer {
|
|
|
384
567
|
},
|
|
385
568
|
types: {
|
|
386
569
|
type: 'array',
|
|
387
|
-
items: {
|
|
570
|
+
items: {
|
|
571
|
+
type: 'string',
|
|
572
|
+
enum: Object.keys(this.glossary),
|
|
573
|
+
},
|
|
388
574
|
description: 'Content types to search in',
|
|
389
575
|
},
|
|
390
576
|
limit: {
|
|
@@ -420,10 +606,13 @@ export class QikMCPServer {
|
|
|
420
606
|
required: ['id'],
|
|
421
607
|
},
|
|
422
608
|
},
|
|
423
|
-
]
|
|
424
|
-
|
|
609
|
+
];
|
|
610
|
+
return { tools };
|
|
611
|
+
});
|
|
425
612
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
426
613
|
try {
|
|
614
|
+
// Ensure glossary is fresh
|
|
615
|
+
await this.loadGlossary();
|
|
427
616
|
switch (request.params.name) {
|
|
428
617
|
case 'qik_get_user_session':
|
|
429
618
|
return await this.getUserSession();
|
|
@@ -462,7 +651,7 @@ export class QikMCPServer {
|
|
|
462
651
|
}
|
|
463
652
|
}
|
|
464
653
|
catch (error) {
|
|
465
|
-
|
|
654
|
+
this.log(`Error in ${request.params.name}: ${this.formatError(error)}`);
|
|
466
655
|
if (axios.isAxiosError(error)) {
|
|
467
656
|
const axiosError = error;
|
|
468
657
|
if (axiosError.response?.status === 401) {
|
|
@@ -503,114 +692,247 @@ export class QikMCPServer {
|
|
|
503
692
|
}
|
|
504
693
|
});
|
|
505
694
|
}
|
|
506
|
-
//
|
|
695
|
+
// Enhanced tool implementations
|
|
507
696
|
async getUserSession() {
|
|
508
|
-
|
|
697
|
+
if (!this.userSession) {
|
|
698
|
+
await this.loadUserSession();
|
|
699
|
+
}
|
|
509
700
|
return {
|
|
510
701
|
content: [{
|
|
511
702
|
type: 'text',
|
|
512
|
-
text: JSON.stringify(
|
|
703
|
+
text: JSON.stringify(this.userSession, null, 2),
|
|
513
704
|
}],
|
|
514
705
|
};
|
|
515
706
|
}
|
|
516
707
|
async getGlossary() {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
708
|
+
await this.loadGlossary(true); // Force refresh
|
|
709
|
+
const summary = Object.entries(this.glossary).map(([key, contentType]) => ({
|
|
710
|
+
key,
|
|
711
|
+
title: contentType.definition?.title || contentType.type.title,
|
|
712
|
+
plural: contentType.definition?.plural || contentType.type.plural,
|
|
713
|
+
fieldCount: (contentType.definition?.fields || contentType.type.fields || []).length,
|
|
714
|
+
definesType: contentType.definition?.definesType || contentType.type.key,
|
|
715
|
+
}));
|
|
520
716
|
return {
|
|
521
717
|
content: [{
|
|
522
718
|
type: 'text',
|
|
523
|
-
text: JSON.stringify(
|
|
719
|
+
text: JSON.stringify({
|
|
720
|
+
summary,
|
|
721
|
+
totalTypes: Object.keys(this.glossary).length,
|
|
722
|
+
fullGlossary: this.glossary,
|
|
723
|
+
}, null, 2),
|
|
524
724
|
}],
|
|
525
725
|
};
|
|
526
726
|
}
|
|
527
727
|
async getContentDefinition(args) {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
728
|
+
const validation = this.validateContentType(args.type);
|
|
729
|
+
if (!validation.valid) {
|
|
730
|
+
return {
|
|
731
|
+
content: [{
|
|
732
|
+
type: 'text',
|
|
733
|
+
text: validation.error,
|
|
734
|
+
}],
|
|
735
|
+
isError: true,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
const response = await this.axiosInstance.get(`/content/${args.type}/definition`);
|
|
740
|
+
return {
|
|
741
|
+
content: [{
|
|
742
|
+
type: 'text',
|
|
743
|
+
text: JSON.stringify(response.data, null, 2),
|
|
744
|
+
}],
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
return {
|
|
749
|
+
content: [{
|
|
750
|
+
type: 'text',
|
|
751
|
+
text: `Failed to fetch definition for '${args.type}': ${this.formatError(error)}`,
|
|
752
|
+
}],
|
|
753
|
+
isError: true,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
535
756
|
}
|
|
536
757
|
async getContent(args) {
|
|
537
758
|
let response;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
759
|
+
try {
|
|
760
|
+
if (args.id) {
|
|
761
|
+
response = await this.axiosInstance.get(`/content/${args.id}`);
|
|
762
|
+
}
|
|
763
|
+
else if (args.slug) {
|
|
764
|
+
response = await this.axiosInstance.get(`/content/slug/${args.slug}`);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
throw new Error('Either id or slug must be provided');
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
content: [{
|
|
771
|
+
type: 'text',
|
|
772
|
+
text: JSON.stringify(response.data, null, 2),
|
|
773
|
+
}],
|
|
774
|
+
};
|
|
543
775
|
}
|
|
544
|
-
|
|
545
|
-
|
|
776
|
+
catch (error) {
|
|
777
|
+
const identifier = args.id || args.slug;
|
|
778
|
+
return {
|
|
779
|
+
content: [{
|
|
780
|
+
type: 'text',
|
|
781
|
+
text: `Failed to fetch content '${identifier}': ${this.formatError(error)}`,
|
|
782
|
+
}],
|
|
783
|
+
isError: true,
|
|
784
|
+
};
|
|
546
785
|
}
|
|
547
|
-
return {
|
|
548
|
-
content: [{
|
|
549
|
-
type: 'text',
|
|
550
|
-
text: JSON.stringify(response.data, null, 2),
|
|
551
|
-
}],
|
|
552
|
-
};
|
|
553
786
|
}
|
|
554
787
|
async listContent(args) {
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
788
|
+
const validation = this.validateContentType(args.type);
|
|
789
|
+
if (!validation.valid) {
|
|
790
|
+
return {
|
|
791
|
+
content: [{
|
|
792
|
+
type: 'text',
|
|
793
|
+
text: validation.error,
|
|
794
|
+
}],
|
|
795
|
+
isError: true,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
const response = await this.axiosInstance.post(`/content/${args.type}/list`, {
|
|
800
|
+
search: args.search || '',
|
|
801
|
+
filter: args.filter || {},
|
|
802
|
+
sort: args.sort || { key: 'meta.created', direction: 'desc', type: 'date' },
|
|
803
|
+
page: args.page || { size: 20, index: 1 },
|
|
804
|
+
select: args.select || [],
|
|
805
|
+
});
|
|
806
|
+
return {
|
|
807
|
+
content: [{
|
|
808
|
+
type: 'text',
|
|
809
|
+
text: JSON.stringify(response.data, null, 2),
|
|
810
|
+
}],
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
return {
|
|
815
|
+
content: [{
|
|
816
|
+
type: 'text',
|
|
817
|
+
text: `Failed to list ${args.type} content: ${this.formatError(error)}`,
|
|
818
|
+
}],
|
|
819
|
+
isError: true,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
568
822
|
}
|
|
569
823
|
async createContent(args) {
|
|
824
|
+
const validation = this.validateContentType(args.type);
|
|
825
|
+
if (!validation.valid) {
|
|
826
|
+
return {
|
|
827
|
+
content: [{
|
|
828
|
+
type: 'text',
|
|
829
|
+
text: validation.error,
|
|
830
|
+
}],
|
|
831
|
+
isError: true,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
// Validate field data if available
|
|
835
|
+
if (args.data && validation.info) {
|
|
836
|
+
const fields = validation.info.definition?.fields || validation.info.type.fields || [];
|
|
837
|
+
const fieldValidation = this.validateFieldData(args.data, fields, args.type);
|
|
838
|
+
if (!fieldValidation.valid) {
|
|
839
|
+
return {
|
|
840
|
+
content: [{
|
|
841
|
+
type: 'text',
|
|
842
|
+
text: `Validation errors for ${args.type}:\n${fieldValidation.errors.join('\n')}`,
|
|
843
|
+
}],
|
|
844
|
+
isError: true,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
570
848
|
const payload = {
|
|
571
849
|
title: args.title,
|
|
572
850
|
data: args.data || {},
|
|
573
851
|
meta: args.meta || {},
|
|
574
852
|
};
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
853
|
+
try {
|
|
854
|
+
const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
|
|
855
|
+
return {
|
|
856
|
+
content: [{
|
|
857
|
+
type: 'text',
|
|
858
|
+
text: JSON.stringify(response.data, null, 2),
|
|
859
|
+
}],
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
return {
|
|
864
|
+
content: [{
|
|
865
|
+
type: 'text',
|
|
866
|
+
text: `Failed to create ${args.type}: ${this.formatError(error)}`,
|
|
867
|
+
}],
|
|
868
|
+
isError: true,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
582
871
|
}
|
|
583
872
|
async updateContent(args) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
873
|
+
try {
|
|
874
|
+
const method = args.replace ? 'put' : 'patch';
|
|
875
|
+
const response = await this.axiosInstance[method](`/content/${args.id}`, args.data);
|
|
876
|
+
return {
|
|
877
|
+
content: [{
|
|
878
|
+
type: 'text',
|
|
879
|
+
text: JSON.stringify(response.data, null, 2),
|
|
880
|
+
}],
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
return {
|
|
885
|
+
content: [{
|
|
886
|
+
type: 'text',
|
|
887
|
+
text: `Failed to update content ${args.id}: ${this.formatError(error)}`,
|
|
888
|
+
}],
|
|
889
|
+
isError: true,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
592
892
|
}
|
|
593
893
|
async deleteContent(args) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
894
|
+
try {
|
|
895
|
+
await this.axiosInstance.delete(`/content/${args.id}`);
|
|
896
|
+
return {
|
|
897
|
+
content: [{
|
|
898
|
+
type: 'text',
|
|
899
|
+
text: `Content ${args.id} deleted successfully`,
|
|
900
|
+
}],
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
return {
|
|
905
|
+
content: [{
|
|
906
|
+
type: 'text',
|
|
907
|
+
text: `Failed to delete content ${args.id}: ${this.formatError(error)}`,
|
|
908
|
+
}],
|
|
909
|
+
isError: true,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
601
912
|
}
|
|
602
913
|
async listProfiles(args) {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
914
|
+
try {
|
|
915
|
+
const response = await this.axiosInstance.post('/content/profile/list', {
|
|
916
|
+
search: args.search || '',
|
|
917
|
+
filter: args.filter || {},
|
|
918
|
+
page: args.page || { size: 20, index: 1 },
|
|
919
|
+
});
|
|
920
|
+
return {
|
|
921
|
+
content: [{
|
|
922
|
+
type: 'text',
|
|
923
|
+
text: JSON.stringify(response.data, null, 2),
|
|
924
|
+
}],
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
return {
|
|
929
|
+
content: [{
|
|
930
|
+
type: 'text',
|
|
931
|
+
text: `Failed to list profiles: ${this.formatError(error)}`,
|
|
932
|
+
}],
|
|
933
|
+
isError: true,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
614
936
|
}
|
|
615
937
|
async createProfile(args) {
|
|
616
938
|
const payload = {
|
|
@@ -621,59 +943,104 @@ export class QikMCPServer {
|
|
|
621
943
|
data: args.data || {},
|
|
622
944
|
meta: args.meta || {},
|
|
623
945
|
};
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
946
|
+
try {
|
|
947
|
+
const response = await this.axiosInstance.post('/content/profile/create', payload);
|
|
948
|
+
return {
|
|
949
|
+
content: [{
|
|
950
|
+
type: 'text',
|
|
951
|
+
text: JSON.stringify(response.data, null, 2),
|
|
952
|
+
}],
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
catch (error) {
|
|
956
|
+
return {
|
|
957
|
+
content: [{
|
|
958
|
+
type: 'text',
|
|
959
|
+
text: `Failed to create profile: ${this.formatError(error)}`,
|
|
960
|
+
}],
|
|
961
|
+
isError: true,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
631
964
|
}
|
|
632
965
|
async getForm(args) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
966
|
+
try {
|
|
967
|
+
const response = await this.axiosInstance.get(`/form/${args.id}`);
|
|
968
|
+
return {
|
|
969
|
+
content: [{
|
|
970
|
+
type: 'text',
|
|
971
|
+
text: JSON.stringify(response.data, null, 2),
|
|
972
|
+
}],
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
976
|
+
return {
|
|
977
|
+
content: [{
|
|
978
|
+
type: 'text',
|
|
979
|
+
text: `Failed to get form ${args.id}: ${this.formatError(error)}`,
|
|
980
|
+
}],
|
|
981
|
+
isError: true,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
640
984
|
}
|
|
641
985
|
async submitForm(args) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
986
|
+
try {
|
|
987
|
+
const response = await this.axiosInstance.post(`/form/${args.id}`, args.data);
|
|
988
|
+
return {
|
|
989
|
+
content: [{
|
|
990
|
+
type: 'text',
|
|
991
|
+
text: JSON.stringify(response.data, null, 2),
|
|
992
|
+
}],
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
return {
|
|
997
|
+
content: [{
|
|
998
|
+
type: 'text',
|
|
999
|
+
text: `Failed to submit form ${args.id}: ${this.formatError(error)}`,
|
|
1000
|
+
}],
|
|
1001
|
+
isError: true,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
649
1004
|
}
|
|
650
1005
|
async uploadFile(args) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1006
|
+
try {
|
|
1007
|
+
const formData = new FormData();
|
|
1008
|
+
// Convert base64 to buffer
|
|
1009
|
+
const fileBuffer = Buffer.from(args.fileData, 'base64');
|
|
1010
|
+
formData.append('file', fileBuffer, args.fileName);
|
|
1011
|
+
const jsonData = {
|
|
1012
|
+
title: args.title,
|
|
1013
|
+
meta: args.meta || {},
|
|
1014
|
+
};
|
|
1015
|
+
formData.append('json', JSON.stringify(jsonData));
|
|
1016
|
+
const response = await this.axiosInstance.post('/file/upload', formData, {
|
|
1017
|
+
headers: {
|
|
1018
|
+
...formData.getHeaders(),
|
|
1019
|
+
'Authorization': `Bearer ${QIK_ACCESS_TOKEN}`,
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
return {
|
|
1023
|
+
content: [{
|
|
1024
|
+
type: 'text',
|
|
1025
|
+
text: JSON.stringify(response.data, null, 2),
|
|
1026
|
+
}],
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
catch (error) {
|
|
1030
|
+
return {
|
|
1031
|
+
content: [{
|
|
1032
|
+
type: 'text',
|
|
1033
|
+
text: `Failed to upload file: ${this.formatError(error)}`,
|
|
1034
|
+
}],
|
|
1035
|
+
isError: true,
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
672
1038
|
}
|
|
673
1039
|
async searchContent(args) {
|
|
674
1040
|
const results = [];
|
|
675
1041
|
const types = args.types || Object.keys(this.glossary);
|
|
676
|
-
|
|
1042
|
+
// Limit to 5 types to avoid too many requests
|
|
1043
|
+
for (const type of types.slice(0, 5)) {
|
|
677
1044
|
try {
|
|
678
1045
|
const response = await this.axiosInstance.post(`/content/${type}/list`, {
|
|
679
1046
|
search: args.query,
|
|
@@ -700,27 +1067,49 @@ export class QikMCPServer {
|
|
|
700
1067
|
};
|
|
701
1068
|
}
|
|
702
1069
|
async getScopes() {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1070
|
+
try {
|
|
1071
|
+
const response = await this.axiosInstance.get('/scope/tree');
|
|
1072
|
+
return {
|
|
1073
|
+
content: [{
|
|
1074
|
+
type: 'text',
|
|
1075
|
+
text: JSON.stringify(response.data, null, 2),
|
|
1076
|
+
}],
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
catch (error) {
|
|
1080
|
+
return {
|
|
1081
|
+
content: [{
|
|
1082
|
+
type: 'text',
|
|
1083
|
+
text: `Failed to get scopes: ${this.formatError(error)}`,
|
|
1084
|
+
}],
|
|
1085
|
+
isError: true,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
710
1088
|
}
|
|
711
1089
|
async getSmartlist(args) {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
const response = await this.axiosInstance.get(`/smartlist/${args.id}`);
|
|
1092
|
+
return {
|
|
1093
|
+
content: [{
|
|
1094
|
+
type: 'text',
|
|
1095
|
+
text: JSON.stringify(response.data, null, 2),
|
|
1096
|
+
}],
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
return {
|
|
1101
|
+
content: [{
|
|
1102
|
+
type: 'text',
|
|
1103
|
+
text: `Failed to get smartlist ${args.id}: ${this.formatError(error)}`,
|
|
1104
|
+
}],
|
|
1105
|
+
isError: true,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
719
1108
|
}
|
|
720
1109
|
async run() {
|
|
721
1110
|
const transport = new StdioServerTransport();
|
|
722
1111
|
await this.server.connect(transport);
|
|
723
|
-
|
|
1112
|
+
this.log('Qik MCP server running on stdio');
|
|
724
1113
|
}
|
|
725
1114
|
}
|
|
726
1115
|
// Only run the server if this file is executed directly (not imported)
|