@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.
- package/README.md +6 -6
- package/index.js +125 -31
- 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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
...
|
|
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
|
-
//
|
|
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
|
-
...
|
|
1222
|
+
...apiAttributes,
|
|
1125
1223
|
...(fields && { 'custom-fields': fields })
|
|
1126
1224
|
}
|
|
1127
1225
|
}
|
|
1128
1226
|
};
|
|
1129
1227
|
|
|
1130
|
-
|
|
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
|
-
|
|
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'
|