@qikdev/mcp 1.0.0 → 2.0.0

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,7 +51,7 @@ export class QikMCPServer {
49
51
  timeout: 30000,
50
52
  });
51
53
  this.setupToolHandlers();
52
- this.initializeGlossary();
54
+ this.initializeServer();
53
55
  // Error handling
54
56
  this.server.onerror = (error) => console.error('[MCP Error]', error);
55
57
  process.on('SIGINT', async () => {
@@ -57,10 +59,37 @@ export class QikMCPServer {
57
59
  process.exit(0);
58
60
  });
59
61
  }
60
- async initializeGlossary() {
62
+ async initializeServer() {
63
+ try {
64
+ // Load user session first
65
+ await this.loadUserSession();
66
+ // Then load glossary
67
+ await this.loadGlossary();
68
+ console.error(`[Qik MCP] Initialized with ${Object.keys(this.glossary).length} content types`);
69
+ }
70
+ catch (error) {
71
+ console.error('[Qik MCP] Failed to initialize server:', this.formatError(error));
72
+ }
73
+ }
74
+ async loadUserSession() {
75
+ try {
76
+ const response = await this.axiosInstance.get('/user');
77
+ this.userSession = response.data.session || response.data;
78
+ console.error(`[Qik MCP] Authenticated as ${this.userSession?.firstName} ${this.userSession?.lastName}`);
79
+ }
80
+ catch (error) {
81
+ console.error('[Qik MCP] Failed to load user session:', this.formatError(error));
82
+ }
83
+ }
84
+ async loadGlossary(force = false) {
85
+ const now = Date.now();
86
+ if (!force && this.lastGlossaryUpdate && (now - this.lastGlossaryUpdate) < this.GLOSSARY_CACHE_TTL) {
87
+ return; // Use cached version
88
+ }
61
89
  try {
62
90
  const response = await this.axiosInstance.get('/glossary');
63
91
  this.glossary = response.data || {};
92
+ this.lastGlossaryUpdate = now;
64
93
  console.error(`[Qik MCP] Loaded ${Object.keys(this.glossary).length} content types from glossary`);
65
94
  }
66
95
  catch (error) {
@@ -71,15 +100,128 @@ export class QikMCPServer {
71
100
  if (axios.isAxiosError(error)) {
72
101
  const axiosError = error;
73
102
  if (axiosError.response) {
74
- return `HTTP ${axiosError.response.status}: ${JSON.stringify(axiosError.response.data)}`;
103
+ const status = axiosError.response.status;
104
+ const data = axiosError.response.data;
105
+ return `HTTP ${status}: ${JSON.stringify(data)}`;
75
106
  }
76
107
  return `Network error: ${axiosError.message}`;
77
108
  }
78
109
  return error.message || String(error);
79
110
  }
111
+ getContentTypeInfo(type) {
112
+ return this.glossary[type] || null;
113
+ }
114
+ validateContentType(type) {
115
+ const info = this.getContentTypeInfo(type);
116
+ if (!info) {
117
+ const availableTypes = Object.keys(this.glossary).join(', ');
118
+ return {
119
+ valid: false,
120
+ error: `Content type '${type}' not found. Available types: ${availableTypes}`
121
+ };
122
+ }
123
+ return { valid: true, info };
124
+ }
125
+ validateFieldData(data, fields, contentType) {
126
+ const errors = [];
127
+ for (const field of fields) {
128
+ const value = data[field.key];
129
+ // Check required fields
130
+ if (field.minimum && field.minimum > 0 && (!value || (Array.isArray(value) && value.length === 0))) {
131
+ errors.push(`Field '${field.key}' (${field.title}) is required for ${contentType}`);
132
+ }
133
+ // Type validation
134
+ if (value !== undefined && value !== null) {
135
+ switch (field.type) {
136
+ case 'string':
137
+ if (typeof value !== 'string') {
138
+ errors.push(`Field '${field.key}' must be a string, got ${typeof value}`);
139
+ }
140
+ break;
141
+ case 'number':
142
+ case 'integer':
143
+ if (typeof value !== 'number') {
144
+ errors.push(`Field '${field.key}' must be a number, got ${typeof value}`);
145
+ }
146
+ break;
147
+ case 'boolean':
148
+ if (typeof value !== 'boolean') {
149
+ errors.push(`Field '${field.key}' must be a boolean, got ${typeof value}`);
150
+ }
151
+ break;
152
+ case 'array':
153
+ if (!Array.isArray(value)) {
154
+ errors.push(`Field '${field.key}' must be an array, got ${typeof value}`);
155
+ }
156
+ break;
157
+ case 'reference':
158
+ if (field.referenceType && typeof value === 'string') {
159
+ // Basic validation - could be enhanced to check if referenced item exists
160
+ }
161
+ else if (Array.isArray(value) && field.maximum !== 1) {
162
+ // Array of references
163
+ }
164
+ else {
165
+ errors.push(`Field '${field.key}' must be a valid reference to ${field.referenceType || 'content'}`);
166
+ }
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ return { valid: errors.length === 0, errors };
172
+ }
173
+ generateFieldSchema(field) {
174
+ const schema = {
175
+ type: this.mapFieldTypeToJsonSchema(field.type),
176
+ description: field.description || field.title,
177
+ };
178
+ if (field.options && field.options.length > 0) {
179
+ schema.enum = field.options.map(opt => opt.value);
180
+ }
181
+ if (field.type === 'array' && field.fields) {
182
+ schema.items = {
183
+ type: 'object',
184
+ properties: this.generateFieldsSchema(field.fields),
185
+ };
186
+ }
187
+ if (field.type === 'reference') {
188
+ schema.description += field.referenceType ? ` (references ${field.referenceType})` : ' (content reference)';
189
+ }
190
+ return schema;
191
+ }
192
+ generateFieldsSchema(fields) {
193
+ const properties = {};
194
+ for (const field of fields) {
195
+ properties[field.key] = this.generateFieldSchema(field);
196
+ }
197
+ return properties;
198
+ }
199
+ mapFieldTypeToJsonSchema(fieldType) {
200
+ switch (fieldType) {
201
+ case 'string':
202
+ case 'email':
203
+ case 'url':
204
+ case 'date':
205
+ return 'string';
206
+ case 'number':
207
+ case 'integer':
208
+ return 'number';
209
+ case 'boolean':
210
+ return 'boolean';
211
+ case 'array':
212
+ return 'array';
213
+ case 'group':
214
+ case 'reference':
215
+ return 'object';
216
+ default:
217
+ return 'string';
218
+ }
219
+ }
80
220
  setupToolHandlers() {
81
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
82
- tools: [
221
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
222
+ // Ensure glossary is loaded
223
+ await this.loadGlossary();
224
+ const tools = [
83
225
  // Authentication & Session
84
226
  {
85
227
  name: 'qik_get_user_session',
@@ -107,6 +249,7 @@ export class QikMCPServer {
107
249
  type: {
108
250
  type: 'string',
109
251
  description: 'Content type key (e.g., "article", "profile", "event")',
252
+ enum: Object.keys(this.glossary),
110
253
  },
111
254
  },
112
255
  required: ['type'],
@@ -142,7 +285,8 @@ export class QikMCPServer {
142
285
  properties: {
143
286
  type: {
144
287
  type: 'string',
145
- description: 'Content type to list (e.g., "article", "profile", "event")',
288
+ description: 'Content type to list',
289
+ enum: Object.keys(this.glossary),
146
290
  },
147
291
  search: {
148
292
  type: 'string',
@@ -185,6 +329,7 @@ export class QikMCPServer {
185
329
  type: {
186
330
  type: 'string',
187
331
  description: 'Content type to create',
332
+ enum: Object.keys(this.glossary),
188
333
  },
189
334
  title: {
190
335
  type: 'string',
@@ -197,6 +342,23 @@ export class QikMCPServer {
197
342
  meta: {
198
343
  type: 'object',
199
344
  description: 'Meta information (scopes, tags, etc.)',
345
+ properties: {
346
+ scopes: {
347
+ type: 'array',
348
+ items: { type: 'string' },
349
+ description: 'Scope IDs where this content should be stored',
350
+ },
351
+ tags: {
352
+ type: 'array',
353
+ items: { type: 'string' },
354
+ description: 'Tag IDs for this content',
355
+ },
356
+ security: {
357
+ type: 'string',
358
+ enum: ['public', 'secure', 'private'],
359
+ description: 'Security level for this content',
360
+ },
361
+ },
200
362
  },
201
363
  },
202
364
  required: ['type', 'title'],
@@ -384,7 +546,10 @@ export class QikMCPServer {
384
546
  },
385
547
  types: {
386
548
  type: 'array',
387
- items: { type: 'string' },
549
+ items: {
550
+ type: 'string',
551
+ enum: Object.keys(this.glossary),
552
+ },
388
553
  description: 'Content types to search in',
389
554
  },
390
555
  limit: {
@@ -420,10 +585,13 @@ export class QikMCPServer {
420
585
  required: ['id'],
421
586
  },
422
587
  },
423
- ],
424
- }));
588
+ ];
589
+ return { tools };
590
+ });
425
591
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
426
592
  try {
593
+ // Ensure glossary is fresh
594
+ await this.loadGlossary();
427
595
  switch (request.params.name) {
428
596
  case 'qik_get_user_session':
429
597
  return await this.getUserSession();
@@ -503,114 +671,247 @@ export class QikMCPServer {
503
671
  }
504
672
  });
505
673
  }
506
- // Tool implementations
674
+ // Enhanced tool implementations
507
675
  async getUserSession() {
508
- const response = await this.axiosInstance.get('/user');
676
+ if (!this.userSession) {
677
+ await this.loadUserSession();
678
+ }
509
679
  return {
510
680
  content: [{
511
681
  type: 'text',
512
- text: JSON.stringify(response.data, null, 2),
682
+ text: JSON.stringify(this.userSession, null, 2),
513
683
  }],
514
684
  };
515
685
  }
516
686
  async getGlossary() {
517
- if (Object.keys(this.glossary).length === 0) {
518
- await this.initializeGlossary();
519
- }
687
+ await this.loadGlossary(true); // Force refresh
688
+ const summary = Object.entries(this.glossary).map(([key, contentType]) => ({
689
+ key,
690
+ title: contentType.definition?.title || contentType.type.title,
691
+ plural: contentType.definition?.plural || contentType.type.plural,
692
+ fieldCount: (contentType.definition?.fields || contentType.type.fields || []).length,
693
+ definesType: contentType.definition?.definesType || contentType.type.key,
694
+ }));
520
695
  return {
521
696
  content: [{
522
697
  type: 'text',
523
- text: JSON.stringify(this.glossary, null, 2),
698
+ text: JSON.stringify({
699
+ summary,
700
+ totalTypes: Object.keys(this.glossary).length,
701
+ fullGlossary: this.glossary,
702
+ }, null, 2),
524
703
  }],
525
704
  };
526
705
  }
527
706
  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
- };
707
+ const validation = this.validateContentType(args.type);
708
+ if (!validation.valid) {
709
+ return {
710
+ content: [{
711
+ type: 'text',
712
+ text: validation.error,
713
+ }],
714
+ isError: true,
715
+ };
716
+ }
717
+ try {
718
+ const response = await this.axiosInstance.get(`/content/${args.type}/definition`);
719
+ return {
720
+ content: [{
721
+ type: 'text',
722
+ text: JSON.stringify(response.data, null, 2),
723
+ }],
724
+ };
725
+ }
726
+ catch (error) {
727
+ return {
728
+ content: [{
729
+ type: 'text',
730
+ text: `Failed to fetch definition for '${args.type}': ${this.formatError(error)}`,
731
+ }],
732
+ isError: true,
733
+ };
734
+ }
535
735
  }
536
736
  async getContent(args) {
537
737
  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}`);
738
+ try {
739
+ if (args.id) {
740
+ response = await this.axiosInstance.get(`/content/${args.id}`);
741
+ }
742
+ else if (args.slug) {
743
+ response = await this.axiosInstance.get(`/content/slug/${args.slug}`);
744
+ }
745
+ else {
746
+ throw new Error('Either id or slug must be provided');
747
+ }
748
+ return {
749
+ content: [{
750
+ type: 'text',
751
+ text: JSON.stringify(response.data, null, 2),
752
+ }],
753
+ };
543
754
  }
544
- else {
545
- throw new Error('Either id or slug must be provided');
755
+ catch (error) {
756
+ const identifier = args.id || args.slug;
757
+ return {
758
+ content: [{
759
+ type: 'text',
760
+ text: `Failed to fetch content '${identifier}': ${this.formatError(error)}`,
761
+ }],
762
+ isError: true,
763
+ };
546
764
  }
547
- return {
548
- content: [{
549
- type: 'text',
550
- text: JSON.stringify(response.data, null, 2),
551
- }],
552
- };
553
765
  }
554
766
  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
- };
767
+ const validation = this.validateContentType(args.type);
768
+ if (!validation.valid) {
769
+ return {
770
+ content: [{
771
+ type: 'text',
772
+ text: validation.error,
773
+ }],
774
+ isError: true,
775
+ };
776
+ }
777
+ try {
778
+ const response = await this.axiosInstance.post(`/content/${args.type}/list`, {
779
+ search: args.search || '',
780
+ filter: args.filter || {},
781
+ sort: args.sort || { key: 'meta.created', direction: 'desc', type: 'date' },
782
+ page: args.page || { size: 20, index: 1 },
783
+ select: args.select || [],
784
+ });
785
+ return {
786
+ content: [{
787
+ type: 'text',
788
+ text: JSON.stringify(response.data, null, 2),
789
+ }],
790
+ };
791
+ }
792
+ catch (error) {
793
+ return {
794
+ content: [{
795
+ type: 'text',
796
+ text: `Failed to list ${args.type} content: ${this.formatError(error)}`,
797
+ }],
798
+ isError: true,
799
+ };
800
+ }
568
801
  }
569
802
  async createContent(args) {
803
+ const validation = this.validateContentType(args.type);
804
+ if (!validation.valid) {
805
+ return {
806
+ content: [{
807
+ type: 'text',
808
+ text: validation.error,
809
+ }],
810
+ isError: true,
811
+ };
812
+ }
813
+ // Validate field data if available
814
+ if (args.data && validation.info) {
815
+ const fields = validation.info.definition?.fields || validation.info.type.fields || [];
816
+ const fieldValidation = this.validateFieldData(args.data, fields, args.type);
817
+ if (!fieldValidation.valid) {
818
+ return {
819
+ content: [{
820
+ type: 'text',
821
+ text: `Validation errors for ${args.type}:\n${fieldValidation.errors.join('\n')}`,
822
+ }],
823
+ isError: true,
824
+ };
825
+ }
826
+ }
570
827
  const payload = {
571
828
  title: args.title,
572
829
  data: args.data || {},
573
830
  meta: args.meta || {},
574
831
  };
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
- };
832
+ try {
833
+ const response = await this.axiosInstance.post(`/content/${args.type}/create`, payload);
834
+ return {
835
+ content: [{
836
+ type: 'text',
837
+ text: JSON.stringify(response.data, null, 2),
838
+ }],
839
+ };
840
+ }
841
+ catch (error) {
842
+ return {
843
+ content: [{
844
+ type: 'text',
845
+ text: `Failed to create ${args.type}: ${this.formatError(error)}`,
846
+ }],
847
+ isError: true,
848
+ };
849
+ }
582
850
  }
583
851
  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
- };
852
+ try {
853
+ const method = args.replace ? 'put' : 'patch';
854
+ const response = await this.axiosInstance[method](`/content/${args.id}`, args.data);
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 update content ${args.id}: ${this.formatError(error)}`,
867
+ }],
868
+ isError: true,
869
+ };
870
+ }
592
871
  }
593
872
  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
- };
873
+ try {
874
+ await this.axiosInstance.delete(`/content/${args.id}`);
875
+ return {
876
+ content: [{
877
+ type: 'text',
878
+ text: `Content ${args.id} deleted successfully`,
879
+ }],
880
+ };
881
+ }
882
+ catch (error) {
883
+ return {
884
+ content: [{
885
+ type: 'text',
886
+ text: `Failed to delete content ${args.id}: ${this.formatError(error)}`,
887
+ }],
888
+ isError: true,
889
+ };
890
+ }
601
891
  }
602
892
  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
- };
893
+ try {
894
+ const response = await this.axiosInstance.post('/content/profile/list', {
895
+ search: args.search || '',
896
+ filter: args.filter || {},
897
+ page: args.page || { size: 20, index: 1 },
898
+ });
899
+ return {
900
+ content: [{
901
+ type: 'text',
902
+ text: JSON.stringify(response.data, null, 2),
903
+ }],
904
+ };
905
+ }
906
+ catch (error) {
907
+ return {
908
+ content: [{
909
+ type: 'text',
910
+ text: `Failed to list profiles: ${this.formatError(error)}`,
911
+ }],
912
+ isError: true,
913
+ };
914
+ }
614
915
  }
615
916
  async createProfile(args) {
616
917
  const payload = {
@@ -621,59 +922,104 @@ export class QikMCPServer {
621
922
  data: args.data || {},
622
923
  meta: args.meta || {},
623
924
  };
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
- };
925
+ try {
926
+ const response = await this.axiosInstance.post('/content/profile/create', payload);
927
+ return {
928
+ content: [{
929
+ type: 'text',
930
+ text: JSON.stringify(response.data, null, 2),
931
+ }],
932
+ };
933
+ }
934
+ catch (error) {
935
+ return {
936
+ content: [{
937
+ type: 'text',
938
+ text: `Failed to create profile: ${this.formatError(error)}`,
939
+ }],
940
+ isError: true,
941
+ };
942
+ }
631
943
  }
632
944
  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
- };
945
+ try {
946
+ const response = await this.axiosInstance.get(`/form/${args.id}`);
947
+ return {
948
+ content: [{
949
+ type: 'text',
950
+ text: JSON.stringify(response.data, null, 2),
951
+ }],
952
+ };
953
+ }
954
+ catch (error) {
955
+ return {
956
+ content: [{
957
+ type: 'text',
958
+ text: `Failed to get form ${args.id}: ${this.formatError(error)}`,
959
+ }],
960
+ isError: true,
961
+ };
962
+ }
640
963
  }
641
964
  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
- };
965
+ try {
966
+ const response = await this.axiosInstance.post(`/form/${args.id}`, args.data);
967
+ return {
968
+ content: [{
969
+ type: 'text',
970
+ text: JSON.stringify(response.data, null, 2),
971
+ }],
972
+ };
973
+ }
974
+ catch (error) {
975
+ return {
976
+ content: [{
977
+ type: 'text',
978
+ text: `Failed to submit form ${args.id}: ${this.formatError(error)}`,
979
+ }],
980
+ isError: true,
981
+ };
982
+ }
649
983
  }
650
984
  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
- };
985
+ try {
986
+ const formData = new FormData();
987
+ // Convert base64 to buffer
988
+ const fileBuffer = Buffer.from(args.fileData, 'base64');
989
+ formData.append('file', fileBuffer, args.fileName);
990
+ const jsonData = {
991
+ title: args.title,
992
+ meta: args.meta || {},
993
+ };
994
+ formData.append('json', JSON.stringify(jsonData));
995
+ const response = await this.axiosInstance.post('/file/upload', formData, {
996
+ headers: {
997
+ ...formData.getHeaders(),
998
+ 'Authorization': `Bearer ${QIK_ACCESS_TOKEN}`,
999
+ },
1000
+ });
1001
+ return {
1002
+ content: [{
1003
+ type: 'text',
1004
+ text: JSON.stringify(response.data, null, 2),
1005
+ }],
1006
+ };
1007
+ }
1008
+ catch (error) {
1009
+ return {
1010
+ content: [{
1011
+ type: 'text',
1012
+ text: `Failed to upload file: ${this.formatError(error)}`,
1013
+ }],
1014
+ isError: true,
1015
+ };
1016
+ }
672
1017
  }
673
1018
  async searchContent(args) {
674
1019
  const results = [];
675
1020
  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
1021
+ // Limit to 5 types to avoid too many requests
1022
+ for (const type of types.slice(0, 5)) {
677
1023
  try {
678
1024
  const response = await this.axiosInstance.post(`/content/${type}/list`, {
679
1025
  search: args.query,
@@ -700,22 +1046,44 @@ export class QikMCPServer {
700
1046
  };
701
1047
  }
702
1048
  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
- };
1049
+ try {
1050
+ const response = await this.axiosInstance.get('/scope/tree');
1051
+ return {
1052
+ content: [{
1053
+ type: 'text',
1054
+ text: JSON.stringify(response.data, null, 2),
1055
+ }],
1056
+ };
1057
+ }
1058
+ catch (error) {
1059
+ return {
1060
+ content: [{
1061
+ type: 'text',
1062
+ text: `Failed to get scopes: ${this.formatError(error)}`,
1063
+ }],
1064
+ isError: true,
1065
+ };
1066
+ }
710
1067
  }
711
1068
  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
- };
1069
+ try {
1070
+ const response = await this.axiosInstance.get(`/smartlist/${args.id}`);
1071
+ return {
1072
+ content: [{
1073
+ type: 'text',
1074
+ text: JSON.stringify(response.data, null, 2),
1075
+ }],
1076
+ };
1077
+ }
1078
+ catch (error) {
1079
+ return {
1080
+ content: [{
1081
+ type: 'text',
1082
+ text: `Failed to get smartlist ${args.id}: ${this.formatError(error)}`,
1083
+ }],
1084
+ isError: true,
1085
+ };
1086
+ }
719
1087
  }
720
1088
  async run() {
721
1089
  const transport = new StdioServerTransport();