@testomatio/mcp 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/index.js +84 -56
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -45,13 +45,13 @@ class TestomatioMCPServer {
45
45
  }
46
46
 
47
47
  const data = await response.json();
48
-
48
+
49
49
  if (!data.jwt) {
50
50
  throw new Error('Authentication failed: No JWT token received in response');
51
51
  }
52
52
 
53
53
  this.jwtToken = data.jwt;
54
-
54
+
55
55
  return this.jwtToken;
56
56
  }
57
57
 
@@ -433,9 +433,9 @@ class TestomatioMCPServer {
433
433
  async makeRequest(path, params = {}) {
434
434
  // Ensure we have a valid JWT token
435
435
  const jwt = await this.authenticate();
436
-
436
+
437
437
  const url = new URL(`${this.config.baseUrl}/api/${this.config.projectId}${path}`);
438
-
438
+
439
439
  // Add query parameters with proper array handling
440
440
  Object.entries(params).forEach(([key, value]) => {
441
441
  if (value !== undefined && value !== null) {
@@ -486,7 +486,7 @@ class TestomatioMCPServer {
486
486
  this.jwtToken = null;
487
487
  return this.makePostRequest(path, data);
488
488
  }
489
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
489
+ throw new Error(`HTTP ${response.status}: ${response.statusText}; ${await response.text()}`);
490
490
  }
491
491
 
492
492
  return await response.json();
@@ -580,10 +580,10 @@ class TestomatioMCPServer {
580
580
  formatModel(model, tagName, fields) {
581
581
  const attributes = model.attributes || {};
582
582
  const lines = [`<${tagName}>`];
583
-
583
+
584
584
  // Always include ID from root level
585
585
  lines.push(` <id>${model.id || ''}</id>`);
586
-
586
+
587
587
  // Process specified fields
588
588
  fields.forEach(field => {
589
589
  let value;
@@ -599,7 +599,7 @@ class TestomatioMCPServer {
599
599
  }
600
600
 
601
601
  const formattedValue = this.formatValue(value, field);
602
-
602
+
603
603
  if (field === 'test' && typeof value === 'object') {
604
604
  // Special case for nested test objects
605
605
  lines.push(` <test>${formattedValue}\n </test>`);
@@ -607,7 +607,7 @@ class TestomatioMCPServer {
607
607
  lines.push(` <${xmlFieldName}>${formattedValue}</${xmlFieldName}>`);
608
608
  }
609
609
  });
610
-
610
+
611
611
  lines.push(`</${tagName}>`);
612
612
  return lines.join('\n');
613
613
  }
@@ -615,13 +615,13 @@ class TestomatioMCPServer {
615
615
  async getTests(filters = {}) {
616
616
  const params = this.buildSearchParams(filters);
617
617
  const data = await this.makeRequest('/tests', params);
618
- const formattedTests = data.data.map(test =>
618
+ const formattedTests = data.data.map(test =>
619
619
  this.formatModel(test, 'test', [
620
- 'title', 'description', 'code', 'priority',
620
+ 'title', 'description', 'code', 'priority',
621
621
  'state', 'suite-id', 'tags', 'file'
622
622
  ])
623
623
  ).join('\n\n');
624
-
624
+
625
625
  return {
626
626
  content: [
627
627
  {
@@ -634,7 +634,7 @@ class TestomatioMCPServer {
634
634
 
635
635
  buildSearchParams(filters = {}) {
636
636
  const params = {};
637
-
637
+
638
638
  // Handle basic filters
639
639
  Object.entries(filters).forEach(([key, value]) => {
640
640
  if (value !== undefined && value !== null) {
@@ -680,16 +680,16 @@ class TestomatioMCPServer {
680
680
  async searchTests(filters = {}) {
681
681
  const params = this.buildSearchParams(filters);
682
682
  const data = await this.makeRequest('/tests', params);
683
-
684
- const formattedTests = data.data.map(test =>
683
+
684
+ const formattedTests = data.data.map(test =>
685
685
  this.formatModel(test, 'test', [
686
- 'title', 'description', 'code', 'priority',
686
+ 'title', 'description', 'code', 'priority',
687
687
  'state', 'suite-id', 'tags', 'file'
688
688
  ])
689
689
  ).join('\n\n');
690
-
690
+
691
691
  const searchDescription = this.buildSearchDescription(filters);
692
-
692
+
693
693
  return {
694
694
  content: [
695
695
  {
@@ -704,15 +704,15 @@ class TestomatioMCPServer {
704
704
  // Add filter=true for suites search to include tests
705
705
  const params = this.buildSearchParams({ ...filters, filter: true });
706
706
  const data = await this.makeRequest('/suites', params);
707
-
708
- const formattedSuites = data.data.map(suite =>
707
+
708
+ const formattedSuites = data.data.map(suite =>
709
709
  this.formatModel(suite, 'suite', [
710
710
  'title', 'description', 'test-count', 'is-root', 'file-type'
711
711
  ])
712
712
  ).join('\n\n');
713
-
713
+
714
714
  const searchDescription = this.buildSearchDescription(filters);
715
-
715
+
716
716
  return {
717
717
  content: [
718
718
  {
@@ -725,7 +725,7 @@ class TestomatioMCPServer {
725
725
 
726
726
  buildSearchDescription(filters) {
727
727
  const descriptions = [];
728
-
728
+
729
729
  if (filters.query) {
730
730
  if (filters.query.startsWith('@')) {
731
731
  descriptions.push(`tagged with "${filters.query}"`);
@@ -735,41 +735,41 @@ class TestomatioMCPServer {
735
735
  descriptions.push(`containing "${filters.query}"`);
736
736
  }
737
737
  }
738
-
738
+
739
739
  if (filters.tql) {
740
740
  descriptions.push(`matching TQL: "${filters.tql}"`);
741
741
  }
742
-
742
+
743
743
  if (filters.labels && filters.labels.length > 0) {
744
744
  descriptions.push(`with labels: ${filters.labels.join(', ')}`);
745
745
  }
746
-
746
+
747
747
  if (filters.state) {
748
748
  descriptions.push(`state: ${filters.state}`);
749
749
  }
750
-
750
+
751
751
  if (filters.priority) {
752
752
  descriptions.push(`priority: ${filters.priority}`);
753
753
  }
754
-
754
+
755
755
  if (filters.filter && typeof filters.filter === 'object') {
756
756
  const filterDesc = Object.entries(filters.filter)
757
757
  .map(([key, value]) => `${key}: ${value}`)
758
758
  .join(', ');
759
759
  descriptions.push(`filtered by: ${filterDesc}`);
760
760
  }
761
-
761
+
762
762
  return descriptions.length > 0 ? ` (${descriptions.join(', ')})` : '';
763
763
  }
764
764
 
765
765
  async getRootSuites() {
766
766
  const data = await this.makeRequest('/suites');
767
- const formattedSuites = data.data.map(suite =>
767
+ const formattedSuites = data.data.map(suite =>
768
768
  this.formatModel(suite, 'suite', [
769
769
  'title', 'description', 'test-count', 'is-root', 'file-type'
770
770
  ])
771
771
  ).join('\n\n');
772
-
772
+
773
773
  return {
774
774
  content: [
775
775
  {
@@ -785,7 +785,7 @@ class TestomatioMCPServer {
785
785
  const formattedSuite = this.formatModel(data.data, 'suite', [
786
786
  'title', 'description', 'test-count', 'is-root', 'file-type'
787
787
  ]);
788
-
788
+
789
789
  // Format child suites and tests if they exist
790
790
  let childContent = '';
791
791
  if (data.data.relationships?.children?.data) {
@@ -797,18 +797,18 @@ class TestomatioMCPServer {
797
797
  childContent += `\n\nChild Suites:\n${childSuites}`;
798
798
  }
799
799
  }
800
-
800
+
801
801
  if (data.data.relationships?.tests?.data) {
802
802
  const tests = data.data.relationships.tests.data
803
803
  .map(test => this.formatModel(test, 'test', [
804
- 'title', 'description', 'code', 'priority',
804
+ 'title', 'description', 'code', 'priority',
805
805
  'state', 'suite-id', 'tags', 'file'
806
806
  ])).join('\n\n');
807
807
  if (tests) {
808
808
  childContent += `\n\nTests:\n${tests}`;
809
809
  }
810
810
  }
811
-
811
+
812
812
  return {
813
813
  content: [
814
814
  {
@@ -821,13 +821,13 @@ class TestomatioMCPServer {
821
821
 
822
822
  async getRuns() {
823
823
  const data = await this.makeRequest('/runs');
824
- const formattedRuns = data.data.map(run =>
824
+ const formattedRuns = data.data.map(run =>
825
825
  this.formatModel(run, 'run', [
826
826
  'status', 'title', 'tests-count', 'automated', 'duration',
827
827
  'passed', 'failed', 'skipped', 'created-at', 'finished-at'
828
828
  ])
829
829
  ).join('\n\n');
830
-
830
+
831
831
  return {
832
832
  content: [
833
833
  {
@@ -845,7 +845,7 @@ class TestomatioMCPServer {
845
845
  'status', 'title', 'tests-count', 'automated', 'duration',
846
846
  'passed', 'failed', 'skipped', 'created-at', 'finished-at'
847
847
  ]);
848
-
848
+
849
849
  return {
850
850
  content: [
851
851
  {
@@ -861,14 +861,14 @@ class TestomatioMCPServer {
861
861
  if (dateRange) {
862
862
  params.finished_at_date_range = dateRange;
863
863
  }
864
-
864
+
865
865
  const data = await this.makeRequest('/testruns', params);
866
- const formattedTestruns = data.data.map(testrun =>
866
+ const formattedTestruns = data.data.map(testrun =>
867
867
  this.formatModel(testrun, 'testrun', [
868
868
  'status', 'run-time', 'message', 'run-id', 'test'
869
869
  ])
870
870
  ).join('\n\n');
871
-
871
+
872
872
  return {
873
873
  content: [
874
874
  {
@@ -881,12 +881,12 @@ class TestomatioMCPServer {
881
881
 
882
882
  async getPlans(filters = {}) {
883
883
  const data = await this.makeRequest('/plans', filters);
884
- const formattedPlans = data.data.map(plan =>
884
+ const formattedPlans = data.data.map(plan =>
885
885
  this.formatModel(plan, 'plan', [
886
886
  'title', 'test-count', 'kind', 'created-at', 'tests-ids', 'labels'
887
887
  ])
888
888
  ).join('\n\n');
889
-
889
+
890
890
  return {
891
891
  content: [
892
892
  {
@@ -902,7 +902,7 @@ class TestomatioMCPServer {
902
902
  const formattedPlan = this.formatModel(data.data, 'plan', [
903
903
  'title', 'test-count', 'kind', 'created-at', 'tests-ids', 'labels'
904
904
  ]);
905
-
905
+
906
906
  return {
907
907
  content: [
908
908
  {
@@ -914,19 +914,33 @@ class TestomatioMCPServer {
914
914
  }
915
915
 
916
916
  async createTest(args) {
917
- const { test_id, labels_ids, ...attributes } = args;
917
+ const { suite_id, labels_ids, ...attributes } = args;
918
918
  const requestData = {
919
- type: 'test',
920
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
919
+ data: {
920
+ type: 'tests',
921
+ attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
922
+ }
921
923
  };
924
+
925
+ if (suite_id) {
926
+ requestData.data.relationships = {
927
+ suite: {
928
+ data: {
929
+ type: 'suites',
930
+ id: suite_id
931
+ }
932
+ }
933
+ };
934
+ }
935
+
922
936
  if (labels_ids) requestData.labels_ids = labels_ids;
923
937
 
924
938
  const data = await this.makePostRequest('/tests', requestData);
925
939
  const formattedTest = this.formatModel(data.data, 'test', [
926
- 'title', 'description', 'code', 'priority',
940
+ 'title', 'description', 'code', 'priority',
927
941
  'state', 'suite-id', 'tags', 'file'
928
942
  ]);
929
-
943
+
930
944
  return {
931
945
  content: [
932
946
  {
@@ -938,19 +952,33 @@ class TestomatioMCPServer {
938
952
  }
939
953
 
940
954
  async updateTest(args) {
941
- const { test_id, labels_ids, ...attributes } = args;
955
+ const { test_id, suite_id, labels_ids, ...attributes } = args;
942
956
  const requestData = {
943
- type: 'test',
944
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
957
+ data: {
958
+ type: 'tests',
959
+ attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
960
+ }
945
961
  };
962
+
963
+ if (suite_id) {
964
+ requestData.data.relationships = {
965
+ suite: {
966
+ data: {
967
+ type: 'suites',
968
+ id: suite_id
969
+ }
970
+ }
971
+ };
972
+ }
973
+
946
974
  if (labels_ids) requestData.labels_ids = labels_ids;
947
975
 
948
- const data = await this.makePutRequest(`/tests/${args.test_id}`, requestData);
976
+ const data = await this.makePutRequest(`/tests/${test_id}`, requestData);
949
977
  const formattedTest = this.formatModel(data.data, 'test', [
950
- 'title', 'description', 'code', 'priority',
978
+ 'title', 'description', 'code', 'priority',
951
979
  'state', 'suite-id', 'tags', 'file'
952
980
  ]);
953
-
981
+
954
982
  return {
955
983
  content: [
956
984
  {
@@ -989,7 +1017,7 @@ function parseArgs() {
989
1017
  .parse();
990
1018
 
991
1019
  const options = program.opts();
992
-
1020
+
993
1021
  const token = options.token || process.env.TESTOMATIO_API_TOKEN;
994
1022
  const projectId = options.project;
995
1023
  const baseUrl = options.baseUrl || process.env.TESTOMATIO_BASE_URL || 'https://app.testomat.io';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "index.js",
6
6
  "bin": {