@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.
@@ -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
- * - Content management (CRUD operations)
11
- * - Content type discovery via glossary
12
- * - User and profile management
13
- * - Form handling and submissions
14
- * - File upload and management
15
- * - Search and filtering capabilities
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: "1.0.0",
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.initializeGlossary();
54
+ this.initializeServer();
53
55
  // Error handling
54
- this.server.onerror = (error) => console.error('[MCP Error]', 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
- async initializeGlossary() {
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
- this.glossary = response.data || {};
64
- console.error(`[Qik MCP] Loaded ${Object.keys(this.glossary).length} content types from glossary`);
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
- console.error('[Qik MCP] Failed to load glossary:', this.formatError(error));
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
- return `HTTP ${axiosError.response.status}: ${JSON.stringify(axiosError.response.data)}`;
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
- tools: [
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 (e.g., "article", "profile", "event")',
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: { type: 'string' },
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
- console.error(`[Qik MCP] Error in ${request.params.name}:`, error);
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
- // Tool implementations
695
+ // Enhanced tool implementations
507
696
  async getUserSession() {
508
- const response = await this.axiosInstance.get('/user');
697
+ if (!this.userSession) {
698
+ await this.loadUserSession();
699
+ }
509
700
  return {
510
701
  content: [{
511
702
  type: 'text',
512
- text: JSON.stringify(response.data, null, 2),
703
+ text: JSON.stringify(this.userSession, null, 2),
513
704
  }],
514
705
  };
515
706
  }
516
707
  async getGlossary() {
517
- if (Object.keys(this.glossary).length === 0) {
518
- await this.initializeGlossary();
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(this.glossary, null, 2),
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 response = await this.axiosInstance.get(`/content/${args.type}/definition`);
529
- return {
530
- content: [{
531
- type: 'text',
532
- text: JSON.stringify(response.data, null, 2),
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
- if (args.id) {
539
- response = await this.axiosInstance.get(`/content/${args.id}`);
540
- }
541
- else if (args.slug) {
542
- response = await this.axiosInstance.get(`/content/slug/${args.slug}`);
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
- else {
545
- throw new Error('Either id or slug must be provided');
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 response = await this.axiosInstance.post(`/content/${args.type}/list`, {
556
- search: args.search || '',
557
- filter: args.filter || {},
558
- sort: args.sort || { key: 'meta.created', direction: 'desc', type: 'date' },
559
- page: args.page || { size: 20, index: 1 },
560
- select: args.select || [],
561
- });
562
- return {
563
- content: [{
564
- type: 'text',
565
- text: JSON.stringify(response.data, null, 2),
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
- const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
576
- return {
577
- content: [{
578
- type: 'text',
579
- text: JSON.stringify(response.data, null, 2),
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
- const method = args.replace ? 'put' : 'patch';
585
- const response = await this.axiosInstance[method](`/content/${args.id}`, args.data);
586
- return {
587
- content: [{
588
- type: 'text',
589
- text: JSON.stringify(response.data, null, 2),
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
- await this.axiosInstance.delete(`/content/${args.id}`);
595
- return {
596
- content: [{
597
- type: 'text',
598
- text: `Content ${args.id} deleted successfully`,
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
- const response = await this.axiosInstance.post('/content/profile/list', {
604
- search: args.search || '',
605
- filter: args.filter || {},
606
- page: args.page || { size: 20, index: 1 },
607
- });
608
- return {
609
- content: [{
610
- type: 'text',
611
- text: JSON.stringify(response.data, null, 2),
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
- const response = await this.axiosInstance.post('/content/profile/create', payload);
625
- return {
626
- content: [{
627
- type: 'text',
628
- text: JSON.stringify(response.data, null, 2),
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
- const response = await this.axiosInstance.get(`/form/${args.id}`);
634
- return {
635
- content: [{
636
- type: 'text',
637
- text: JSON.stringify(response.data, null, 2),
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
- const response = await this.axiosInstance.post(`/form/${args.id}`, args.data);
643
- return {
644
- content: [{
645
- type: 'text',
646
- text: JSON.stringify(response.data, null, 2),
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
- const formData = new FormData();
652
- // Convert base64 to buffer
653
- const fileBuffer = Buffer.from(args.fileData, 'base64');
654
- formData.append('file', fileBuffer, args.fileName);
655
- const jsonData = {
656
- title: args.title,
657
- meta: args.meta || {},
658
- };
659
- formData.append('json', JSON.stringify(jsonData));
660
- const response = await this.axiosInstance.post('/file/upload', formData, {
661
- headers: {
662
- ...formData.getHeaders(),
663
- 'Authorization': `Bearer ${QIK_ACCESS_TOKEN}`,
664
- },
665
- });
666
- return {
667
- content: [{
668
- type: 'text',
669
- text: JSON.stringify(response.data, null, 2),
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
- for (const type of types.slice(0, 5)) { // Limit to 5 types to avoid too many requests
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
- const response = await this.axiosInstance.get('/scope/tree');
704
- return {
705
- content: [{
706
- type: 'text',
707
- text: JSON.stringify(response.data, null, 2),
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
- const response = await this.axiosInstance.get(`/smartlist/${args.id}`);
713
- return {
714
- content: [{
715
- type: 'text',
716
- text: JSON.stringify(response.data, null, 2),
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
- console.error('Qik MCP server running on stdio');
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)