@testomatio/mcp 1.0.7 → 1.0.9

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 (3) hide show
  1. package/README.md +6 -6
  2. package/index.js +125 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ A Model Context Protocol (MCP) server for Testomat.io API integration with AI as
13
13
  ### Run directly with npx
14
14
 
15
15
  ```bash
16
- npx @testomatio/mcp --token <your-token> --project <project-id>
16
+ npx @testomatio/mcp@latest --token <your-token> --project <project-id>
17
17
  ```
18
18
 
19
19
  ## Usage
@@ -26,13 +26,13 @@ The MCP server can be started using command line arguments or environment variab
26
26
 
27
27
  ```bash
28
28
  # Using short flags
29
- npx @testomatio/mcp -t testomat_YOUR_TOKEN_HERE -p your-project-id
29
+ npx @testomatio/mcp@latest -t testomat_YOUR_TOKEN_HERE -p your-project-id
30
30
 
31
31
  # Using long flags
32
- npx @testomatio/mcp --token testomat_YOUR_TOKEN_HERE --project your-project-id
32
+ npx @testomatio/mcp@latest --token testomat_YOUR_TOKEN_HERE --project your-project-id
33
33
 
34
34
  # With custom base URL
35
- npx @testomatio/mcp --token testomat_YOUR_TOKEN_HERE --project your-project-id --base-url https://your-instance.testomat.io
35
+ npx @testomatio/mcp@latest --token testomat_YOUR_TOKEN_HERE --project your-project-id --base-url https://your-instance.testomat.io
36
36
  ```
37
37
 
38
38
  #### Using Environment Variables
@@ -43,10 +43,10 @@ export TESTOMATIO_API_TOKEN=testomat_YOUR_TOKEN_HERE
43
43
  export TESTOMATIO_BASE_URL=https://app.testomat.io # Optional, defaults to https://app.testomat.io
44
44
 
45
45
  # Run with project ID
46
- npx @testomatio/mcp --project your-project-id
46
+ npx @testomatio/mcp@latest --project your-project-id
47
47
 
48
48
  # Or run directly with environment variables
49
- TESTOMATIO_API_TOKEN=testomat_YOUR_TOKEN_HERE npx @testomatio/mcp --project your-project-id
49
+ TESTOMATIO_API_TOKEN=testomat_YOUR_TOKEN_HERE npx @testomatio/mcp@latest --project your-project-id
50
50
  ```
51
51
 
52
52
  ### Getting Your API Token
package/index.js CHANGED
@@ -480,6 +480,27 @@ class TestomatioMCPServer {
480
480
  required: ['title'],
481
481
  },
482
482
  },
483
+ {
484
+ name: 'get_labels',
485
+ description: 'Get all available labels for the project with their IDs and configurations',
486
+ inputSchema: {
487
+ type: 'object',
488
+ properties: {
489
+ scope: {
490
+ type: 'array',
491
+ items: {
492
+ type: 'string',
493
+ enum: ['tests', 'suites']
494
+ },
495
+ description: 'Filter labels by scope (e.g., ["tests"], ["suites"], or ["tests", "suites"])',
496
+ },
497
+ page: {
498
+ type: 'number',
499
+ description: 'Page number for pagination',
500
+ },
501
+ },
502
+ },
503
+ },
483
504
  {
484
505
  name: 'create_label',
485
506
  description: 'Create a new label with optional custom field configuration. Labels can be used to tag and categorize tests and suites',
@@ -561,6 +582,8 @@ class TestomatioMCPServer {
561
582
  return await this.getPlans(args);
562
583
  case 'get_plan':
563
584
  return await this.getPlan(args.plan_id);
585
+ case 'get_labels':
586
+ return await this.getLabels(args);
564
587
  case 'create_test':
565
588
  return await this.createTest(args);
566
589
  case 'update_test':
@@ -643,7 +666,8 @@ class TestomatioMCPServer {
643
666
  this.jwtToken = null;
644
667
  return this.makePostRequest(path, data);
645
668
  }
646
- throw new Error(`HTTP ${response.status}: ${response.statusText}; ${await response.text()}`);
669
+ const errorText = await response.text();
670
+ throw new Error(`HTTP ${response.status}: ${response.statusText}. Details: ${errorText}`);
647
671
  }
648
672
 
649
673
  return await response.json();
@@ -667,7 +691,8 @@ class TestomatioMCPServer {
667
691
  this.jwtToken = null;
668
692
  return this.makePutRequest(path, data);
669
693
  }
670
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
694
+ const errorText = await response.text();
695
+ throw new Error(`HTTP ${response.status}: ${response.statusText}. Details: ${errorText}`);
671
696
  }
672
697
 
673
698
  return await response.json();
@@ -1070,33 +1095,66 @@ class TestomatioMCPServer {
1070
1095
  };
1071
1096
  }
1072
1097
 
1098
+ async getLabels(filters = {}) {
1099
+ const params = {};
1100
+
1101
+ // Handle scope filter
1102
+ if (filters.scope && Array.isArray(filters.scope)) {
1103
+ if (!params['scope[]']) {
1104
+ params['scope[]'] = [];
1105
+ }
1106
+ filters.scope.forEach(scope => {
1107
+ params['scope[]'].push(scope);
1108
+ });
1109
+ }
1110
+
1111
+ // Handle pagination
1112
+ if (filters.page) {
1113
+ params.page = filters.page;
1114
+ }
1115
+
1116
+ const data = await this.makeRequest('/labels', params);
1117
+ const formattedLabels = data.data.map(label =>
1118
+ this.formatModel(label, 'label', [
1119
+ 'title', 'color', 'scope', 'visibility', 'field'
1120
+ ])
1121
+ ).join('\n\n');
1122
+
1123
+ return {
1124
+ content: [
1125
+ {
1126
+ type: 'text',
1127
+ text: `Available labels for project ${this.config.projectId}:\n\n${formattedLabels || 'No labels found matching the criteria.'}`,
1128
+ },
1129
+ ],
1130
+ };
1131
+ }
1132
+
1073
1133
  async createTest(args) {
1074
1134
  const { suite_id, labels_ids, fields, ...attributes } = args;
1075
1135
 
1076
- // Handle fields parameter for custom fields
1136
+ // Convert attributes to use hyphens instead of underscores for API compatibility
1137
+ const apiAttributes = Object.fromEntries(
1138
+ Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])
1139
+ );
1140
+
1141
+ // Add suite_id to attributes if provided
1142
+ if (suite_id) {
1143
+ apiAttributes['suite_id'] = suite_id;
1144
+ }
1145
+
1146
+ // Build JSON-API request data
1077
1147
  const requestData = {
1078
1148
  data: {
1079
1149
  type: 'tests',
1080
1150
  attributes: {
1081
- ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1151
+ ...apiAttributes,
1152
+ ...(labels_ids && { labels_ids: labels_ids }),
1082
1153
  ...(fields && { 'custom-fields': fields })
1083
1154
  }
1084
1155
  }
1085
1156
  };
1086
1157
 
1087
- if (suite_id) {
1088
- requestData.data.relationships = {
1089
- suite: {
1090
- data: {
1091
- type: 'suites',
1092
- id: suite_id
1093
- }
1094
- }
1095
- };
1096
- }
1097
-
1098
- if (labels_ids) requestData.labels_ids = labels_ids;
1099
-
1100
1158
  const data = await this.makePostRequest('/tests', requestData);
1101
1159
  const formattedTest = this.formatModel(data.data, 'test', [
1102
1160
  'title', 'description', 'code', 'priority',
@@ -1113,34 +1171,70 @@ class TestomatioMCPServer {
1113
1171
  };
1114
1172
  }
1115
1173
 
1174
+ async linkLabels(testId, labelsIds) {
1175
+ if (!labelsIds || labelsIds.length === 0) {
1176
+ return;
1177
+ }
1178
+
1179
+ // Process each label individually using the label linking API
1180
+ for (const labelId of labelsIds) {
1181
+ // Parse label:value format if present
1182
+ let labelUid = labelId;
1183
+ let value = null;
1184
+
1185
+ if (labelId.includes(':')) {
1186
+ [labelUid, value] = labelId.split(':', 2);
1187
+ }
1188
+
1189
+ // Build URL with test_id query parameter
1190
+ let url = `/labels/${labelUid}/link?test_id=${testId}`;
1191
+
1192
+ // Add value as query parameter if present
1193
+ if (value) {
1194
+ url += `&value=${encodeURIComponent(value)}`;
1195
+ }
1196
+
1197
+ await this.makePostRequest(url, {});
1198
+ }
1199
+ }
1200
+
1116
1201
  async updateTest(args) {
1117
1202
  const { test_id, suite_id, labels_ids, fields, ...attributes } = args;
1118
1203
 
1119
- // Handle fields parameter for custom fields
1204
+ // Convert attributes to use hyphens instead of underscores for API compatibility
1205
+ const apiAttributes = Object.fromEntries(
1206
+ Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])
1207
+ );
1208
+
1209
+ // Add suite_id to attributes if provided
1210
+ if (suite_id) {
1211
+ apiAttributes['suite_id'] = suite_id;
1212
+ }
1213
+
1214
+ let data;
1215
+
1216
+ // Handle regular test attributes update using JSON-API format
1120
1217
  const requestData = {
1121
1218
  data: {
1219
+ id: test_id,
1122
1220
  type: 'tests',
1123
1221
  attributes: {
1124
- ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1222
+ ...apiAttributes,
1125
1223
  ...(fields && { 'custom-fields': fields })
1126
1224
  }
1127
1225
  }
1128
1226
  };
1129
1227
 
1130
- if (suite_id) {
1131
- requestData.data.relationships = {
1132
- suite: {
1133
- data: {
1134
- type: 'suites',
1135
- id: suite_id
1136
- }
1137
- }
1138
- };
1139
- }
1228
+ data = await this.makePutRequest(`/tests/${test_id}`, requestData);
1140
1229
 
1141
- if (labels_ids) requestData.labels_ids = labels_ids;
1230
+ // Handle labels_ids using the label linking API
1231
+ if (labels_ids && labels_ids.length > 0) {
1232
+ await this.linkLabels(test_id, labels_ids);
1233
+
1234
+ // After linking labels, fetch the updated test to reflect changes
1235
+ data = await this.makeRequest(`/tests/${test_id}`);
1236
+ }
1142
1237
 
1143
- const data = await this.makePutRequest(`/tests/${test_id}`, requestData);
1144
1238
  const formattedTest = this.formatModel(data.data, 'test', [
1145
1239
  'title', 'description', 'code', 'priority',
1146
1240
  'state', 'suite-id', 'tags', 'file'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "index.js",
6
6
  "bin": {