@testomatio/mcp 1.0.3 → 1.0.4

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 +99 -3
  2. package/index.js +234 -4
  3. package/package.json +14 -3
package/README.md CHANGED
@@ -119,6 +119,11 @@ Then add this to your Cursor MCP settings:
119
119
  * `search_suites` – Search suites (params: `query`, `labels`, `state`, `priority`, `page`) — api: GET `/suites`
120
120
  * `get_root_suites` – List root-level suites (no params) — api: GET `/suites`
121
121
  * `get_suite` – Get one suite (params: `suite_id`) — api: GET `/suites/{suite_id}`
122
+ * `create_suite` – Create a new suite (params: `title`, `description`, `parent_id`) — api: POST `/suites`
123
+ * `create_folder` – Create a new folder (params: `title`, `description`, `parent_id`) — api: POST `/suites`
124
+
125
+ #### Labels
126
+ * `create_label` – Create a new label with optional custom field (params: `title`, `color`, `scope`, `visibility`, `field`) — api: POST `/labels`
122
127
 
123
128
  #### Test Runs
124
129
  * `get_runs` – List all runs (no params) — api: GET `/runs`
@@ -141,6 +146,9 @@ Once configured, you can ask your AI assistant questions like:
141
146
  - "Get all test plans for this project"
142
147
  - "Create a new test called 'Login validation' in suite suite-123"
143
148
  - "Update test test-456 to change its description and add @regression tag"
149
+ - "Create a new suite called 'Authentication Tests' with description 'All login and signup related tests'"
150
+ - "Create a folder called 'API Tests' to organize API-related test suites"
151
+ - "Create a label called 'Severity' with color '#ffe9ad' and predefined values like 'Blocker', 'Critical', 'Major', 'Minor', 'Normal', 'Trivial'"
144
152
 
145
153
  ## Query Patterns
146
154
 
@@ -161,6 +169,27 @@ These queries allow creating and updating tests:
161
169
  - **"Update test test-456 to change its description"** → `update_test` tool with `test_id: "test-456"`, `description: "new description"`
162
170
  - **"Create an automated test with @smoke tag"** → `create_test` tool with `state: "automated"`, `tags: ["smoke"]`
163
171
 
172
+ ### Suite and Folder Management Queries
173
+
174
+ These queries help organize your test structure:
175
+
176
+ - **"Create a new suite called 'Authentication Tests'"** → `create_suite` tool with `title: "Authentication Tests"`
177
+ - **"Create a suite for login tests with description"** → `create_suite` tool with `title: "Login Tests"`, `description: "All login related test cases"`
178
+ - **"Create a folder called 'API Tests' under parent suite-123"** → `create_folder` tool with `title: "API Tests"`, `parent_id: "suite-123"`
179
+ - **"Create a test suite for payment features"** → `create_suite` tool with `title: "Payment Features", description: "Tests covering payment processing"`
180
+ - **"Create a folder to organize integration tests"** → `create_folder` tool with `title: "Integration Tests"`
181
+
182
+ **Note**: Suites can only contain other suites, while folders can contain both suites and folders (but no tests).
183
+
184
+ ### Label Creation Queries
185
+
186
+ These queries help create custom labels for better test categorization:
187
+
188
+ - **"Create a label called 'Severity' with red color"** → `create_label` tool with `title: "Severity"`, `color: "#ff6b6b"`
189
+ - **"Create a severity label with predefined values"** → `create_label` tool with `title: "Severity"`, `color: "#ffe9ad"`, `field: { "type": "list", "short": true, "value": "Blocker\nCritical\nMajor\nNormal\nMinor\nTrivial" }`
190
+ - **"Create a simple label for test types"** → `create_label` tool with `title: "Test Type"`, `scope: ["tests", "suites"]`
191
+ - **"Create a label visible in test lists"** → `create_label` tool with `title: "Category"`, `visibility: ["list"]`
192
+
164
193
  ### Specific Item Queries
165
194
 
166
195
  These queries target specific entities by ID:
@@ -174,9 +203,8 @@ These queries target specific entities by ID:
174
203
  These queries use advanced filtering capabilities:
175
204
 
176
205
  - **"List all automated tests with the @smoke tag"** → `search_tests` tool with `query: "@smoke"`, `state: "automated"`
177
- - **"Find tests with priority high"** → `search_tests` tool with `priority: "high"`
178
206
  - **"Search for tests containing 'login'"** → `search_tests` tool with `query: "login"`
179
- - **"List tests tagged @critical or labelled 'ux' with high priority"** → `search_tests` tool with `tql: "tag == 'critical' or label == 'ux' and priority == 'high'"`
207
+ - **"List tests tagged @critical or labelled 'ux' with critical severity"** → `search_tests` tool with `tql: "tag == 'critical' or label == 'ux' and severity == 'critical'"`
180
208
  - **"Find tests linked to JIRA-123"** → `search_tests` tool with `tql: jira == 'BDCP-2'`
181
209
 
182
210
  ### Advanced Query Syntax
@@ -187,7 +215,7 @@ The `search_tests` tool supports TQL for complex filtering:
187
215
 
188
216
  ```
189
217
  "tag == 'smoke' and state == 'manual'"
190
- "priority == 'high' or label == 'ux'"
218
+ "severity == 'critical' or label == 'ux'"
191
219
  ```
192
220
 
193
221
  #### Tag-Based Searches
@@ -247,6 +275,74 @@ For detailed information about the underlying Testomat.io API, refer to the [Tes
247
275
 
248
276
  Contributions are welcome! Please feel free to submit a Pull Request.
249
277
 
278
+ ### Development Setup
279
+
280
+ ```bash
281
+ # Clone the repository
282
+ git clone https://github.com/testomatio/mcp.git
283
+ cd mcp
284
+
285
+ # Install dependencies
286
+ npm install
287
+
288
+ # Run unit tests
289
+ npm test
290
+
291
+ # Run integration tests (requires environment variables)
292
+ npm run test:integration
293
+
294
+ # Run all tests
295
+ npm run test:all
296
+ ```
297
+
298
+ ### Testing
299
+
300
+ The project includes comprehensive test coverage:
301
+
302
+ - **Unit Tests**: Fast tests with mocked dependencies
303
+ - **Integration Tests**: Real API tests against Testomat.io
304
+
305
+ #### Running Tests Locally
306
+
307
+ ```bash
308
+ # Unit tests only
309
+ npm run test:unit
310
+
311
+ # Integration tests (requires .env file)
312
+ npm run test:integration
313
+
314
+ # With coverage
315
+ npm run test:coverage
316
+ npm run test:coverage:integration
317
+ ```
318
+
319
+ #### Environment Setup for Integration Tests
320
+
321
+ Create a `.env` file:
322
+
323
+ ```bash
324
+ TESTOMATIO_API_TOKEN=testomat_your_token_here
325
+ TESTOMATIO_PROJECT_ID=your_project_id
326
+ TESTOMATIO_BASE_URL=https://app.testomat.io # optional
327
+ ```
328
+
329
+ ### CI/CD
330
+
331
+ This project uses GitHub Actions for continuous integration:
332
+
333
+ - ✅ **Unit Tests**: Run on every push/PR across Node.js 18, 20, 22
334
+ - ✅ **Integration Tests**: Run daily and on main branch merges
335
+ - ✅ **Coverage Reports**: Automatic upload to Codecov
336
+ - ✅ **Security**: Secrets management for API credentials
337
+
338
+
339
+ ### Code Quality
340
+
341
+ - Follow existing code style patterns
342
+ - Add tests for new functionality
343
+ - Update documentation when needed
344
+ - Ensure all tests pass before submitting PRs
345
+
250
346
  ## License
251
347
 
252
348
  This project is licensed under the MIT License - see the LICENSE file for details.
package/index.js CHANGED
@@ -41,7 +41,8 @@ class TestomatioMCPServer {
41
41
  });
42
42
 
43
43
  if (!response.ok) {
44
- throw new Error(`Authentication failed: HTTP ${response.status}: ${response.statusText}`);
44
+ const errorText = await response.text();
45
+ throw new Error(`Authentication failed: HTTP ${response.status}: ${response.statusText}. Response: ${errorText}`);
45
46
  }
46
47
 
47
48
  const data = await response.json();
@@ -358,6 +359,11 @@ class TestomatioMCPServer {
358
359
  enum: ['manual', 'automated'],
359
360
  description: 'State of the test',
360
361
  },
362
+ priority: {
363
+ type: 'string',
364
+ enum: ['low', 'normal', 'high', 'critical'],
365
+ description: 'Priority level of the test',
366
+ },
361
367
  tags: {
362
368
  type: 'array',
363
369
  items: { type: 'string' },
@@ -381,6 +387,102 @@ class TestomatioMCPServer {
381
387
  required: ['test_id'],
382
388
  },
383
389
  },
390
+ {
391
+ name: 'create_suite',
392
+ description: 'Create a new suite. Suites can only contain other suites (no tests or folders)',
393
+ inputSchema: {
394
+ type: 'object',
395
+ properties: {
396
+ title: {
397
+ type: 'string',
398
+ description: 'Suite title',
399
+ },
400
+ description: {
401
+ type: 'string',
402
+ description: 'Suite description',
403
+ },
404
+ parent_id: {
405
+ type: 'string',
406
+ description: 'Parent suite ID to create this suite under',
407
+ },
408
+ },
409
+ required: ['title'],
410
+ },
411
+ },
412
+ {
413
+ name: 'create_folder',
414
+ description: 'Create a new folder. Folders can contain suites and folders (but no tests)',
415
+ inputSchema: {
416
+ type: 'object',
417
+ properties: {
418
+ title: {
419
+ type: 'string',
420
+ description: 'Folder title',
421
+ },
422
+ description: {
423
+ type: 'string',
424
+ description: 'Folder description',
425
+ },
426
+ parent_id: {
427
+ type: 'string',
428
+ description: 'Parent folder or suite ID to create this folder under',
429
+ },
430
+ },
431
+ required: ['title'],
432
+ },
433
+ },
434
+ {
435
+ name: 'create_label',
436
+ description: 'Create a new label with optional custom field configuration. Labels can be used to tag and categorize tests and suites',
437
+ inputSchema: {
438
+ type: 'object',
439
+ properties: {
440
+ title: {
441
+ type: 'string',
442
+ description: 'Label title (e.g., "Severity", "Priority", "Type")',
443
+ },
444
+ color: {
445
+ type: 'string',
446
+ description: 'Label color in hex format (e.g., "#ffe9ad")',
447
+ },
448
+ scope: {
449
+ type: 'array',
450
+ items: {
451
+ type: 'string',
452
+ enum: ['tests', 'suites']
453
+ },
454
+ description: 'Where this label can be used (e.g., ["tests", "suites"])',
455
+ },
456
+ visibility: {
457
+ type: 'array',
458
+ items: {
459
+ type: 'string'
460
+ },
461
+ description: 'Where the label is visible (e.g., ["list"])',
462
+ },
463
+ field: {
464
+ type: 'object',
465
+ description: 'Custom field configuration for labels with predefined values',
466
+ properties: {
467
+ type: {
468
+ type: 'string',
469
+ description: 'Field type (e.g., "list", "string", "number")',
470
+ },
471
+ short: {
472
+ type: 'boolean',
473
+ description: 'Whether to display short version',
474
+ },
475
+ value: {
476
+ type: 'string',
477
+ description: 'Predefined values for the field (newline separated for list type)',
478
+ },
479
+ },
480
+ required: ['type'],
481
+ },
482
+ },
483
+ required: ['title'],
484
+ },
485
+ },
384
486
  ],
385
487
  };
386
488
  });
@@ -414,6 +516,12 @@ class TestomatioMCPServer {
414
516
  return await this.createTest(args);
415
517
  case 'update_test':
416
518
  return await this.updateTest(args);
519
+ case 'create_suite':
520
+ return await this.createSuite(args);
521
+ case 'create_folder':
522
+ return await this.createFolder(args);
523
+ case 'create_label':
524
+ return await this.createLabel(args);
417
525
  default:
418
526
  throw new Error(`Unknown tool: ${name}`);
419
527
  }
@@ -989,6 +1097,122 @@ class TestomatioMCPServer {
989
1097
  };
990
1098
  }
991
1099
 
1100
+ async createSuite(args) {
1101
+ const { parent_id, ...attributes } = args;
1102
+ const requestData = {
1103
+ data: {
1104
+ type: 'suites',
1105
+ attributes: {
1106
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1107
+ 'file-type': 'file'
1108
+ }
1109
+ }
1110
+ };
1111
+
1112
+ if (parent_id) {
1113
+ requestData.data.relationships = {
1114
+ parent: {
1115
+ data: {
1116
+ type: 'suites',
1117
+ id: parent_id
1118
+ }
1119
+ }
1120
+ };
1121
+ }
1122
+
1123
+ const data = await this.makePostRequest('/suites', requestData);
1124
+ const formattedSuite = this.formatModel(data.data, 'suite', [
1125
+ 'title', 'description', 'test-count', 'is-root', 'file-type'
1126
+ ]);
1127
+
1128
+ return {
1129
+ content: [
1130
+ {
1131
+ type: 'text',
1132
+ text: `Successfully created suite:\n\n${formattedSuite}`,
1133
+ },
1134
+ ],
1135
+ };
1136
+ }
1137
+
1138
+ async createFolder(args) {
1139
+ const { parent_id, ...attributes } = args;
1140
+ const requestData = {
1141
+ data: {
1142
+ type: 'suites',
1143
+ attributes: {
1144
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1145
+ 'file-type': 'folder'
1146
+ }
1147
+ }
1148
+ };
1149
+
1150
+ if (parent_id) {
1151
+ requestData.data.relationships = {
1152
+ parent: {
1153
+ data: {
1154
+ type: 'suites',
1155
+ id: parent_id
1156
+ }
1157
+ }
1158
+ };
1159
+ }
1160
+
1161
+ const data = await this.makePostRequest('/suites', requestData);
1162
+ const formattedSuite = this.formatModel(data.data, 'suite', [
1163
+ 'title', 'description', 'test-count', 'is-root', 'file-type'
1164
+ ]);
1165
+
1166
+ return {
1167
+ content: [
1168
+ {
1169
+ type: 'text',
1170
+ text: `Successfully created folder:\n\n${formattedSuite}`,
1171
+ },
1172
+ ],
1173
+ };
1174
+ }
1175
+
1176
+ async createLabel(args) {
1177
+ const { title, color, scope, visibility, field, ...otherAttributes } = args;
1178
+
1179
+ const requestData = {
1180
+ data: {
1181
+ type: 'labels',
1182
+ attributes: {
1183
+ ...Object.fromEntries(Object.entries(otherAttributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1184
+ title,
1185
+ color,
1186
+ scope,
1187
+ visibility
1188
+ }
1189
+ }
1190
+ };
1191
+
1192
+ // Add field configuration if provided
1193
+ if (field) {
1194
+ requestData.data.attributes.field = {
1195
+ type: field.type,
1196
+ ...(field.short !== undefined && { short: field.short }),
1197
+ ...(field.value && { value: field.value })
1198
+ };
1199
+ }
1200
+
1201
+ const data = await this.makePostRequest('/labels', requestData);
1202
+ const formattedLabel = this.formatModel(data.data, 'label', [
1203
+ 'title', 'color', 'scope', 'visibility', 'field'
1204
+ ]);
1205
+
1206
+ return {
1207
+ content: [
1208
+ {
1209
+ type: 'text',
1210
+ text: `Successfully created label:\n\n${formattedLabel}`,
1211
+ },
1212
+ ],
1213
+ };
1214
+ }
1215
+
992
1216
  async run() {
993
1217
  // Test authentication on startup
994
1218
  try {
@@ -1005,6 +1229,9 @@ class TestomatioMCPServer {
1005
1229
  }
1006
1230
  }
1007
1231
 
1232
+ // Export the class for testing
1233
+ export { TestomatioMCPServer };
1234
+
1008
1235
  // Parse command line arguments using commander
1009
1236
  function parseArgs() {
1010
1237
  program
@@ -1019,7 +1246,7 @@ function parseArgs() {
1019
1246
  const options = program.opts();
1020
1247
 
1021
1248
  const token = options.token || process.env.TESTOMATIO_API_TOKEN;
1022
- const projectId = options.project;
1249
+ const projectId = options.project || process.env.TESTOMATIO_PROJECT_ID;
1023
1250
  const baseUrl = options.baseUrl || process.env.TESTOMATIO_BASE_URL || 'https://app.testomat.io';
1024
1251
 
1025
1252
  if (!token) {
@@ -1028,7 +1255,7 @@ function parseArgs() {
1028
1255
  }
1029
1256
 
1030
1257
  if (!projectId) {
1031
- console.error('Error: Project ID is required. Use --project <project_id>');
1258
+ console.error('Error: Project ID is required. Use --project <project_id> or set TESTOMATIO_PROJECT_ID environment variable');
1032
1259
  process.exit(1);
1033
1260
  }
1034
1261
 
@@ -1047,4 +1274,7 @@ async function main() {
1047
1274
  }
1048
1275
  }
1049
1276
 
1050
- main().catch(console.error);
1277
+ // Only run main() if this file is executed directly (not imported)
1278
+ if (import.meta.url === `file://${process.argv[1]}`) {
1279
+ main().catch(console.error);
1280
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -10,7 +10,14 @@
10
10
  "scripts": {
11
11
  "start": "node index.js",
12
12
  "dev": "node index.js",
13
- "test": "echo \"Error: no test specified\" && exit 1"
13
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
14
+ "test:unit": "node --experimental-vm-modules node_modules/.bin/jest",
15
+ "test:integration": "node --experimental-vm-modules node_modules/.bin/jest --config jest.integration.config.js",
16
+ "test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch",
17
+ "test:integration:watch": "node --experimental-vm-modules node_modules/.bin/jest --config jest.integration.config.js --watch",
18
+ "test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
19
+ "test:coverage:integration": "node --experimental-vm-modules node_modules/.bin/jest --config jest.integration.config.js --coverage",
20
+ "test:all": "node --experimental-vm-modules node_modules/.bin/jest --config jest.integration.config.js"
14
21
  },
15
22
  "keywords": [
16
23
  "testomatio",
@@ -24,7 +31,8 @@
24
31
  "license": "MIT",
25
32
  "dependencies": {
26
33
  "@modelcontextprotocol/sdk": "^0.4.0",
27
- "commander": "^12.0.0"
34
+ "commander": "^12.0.0",
35
+ "dotenv": "^17.2.3"
28
36
  },
29
37
  "files": [
30
38
  "index.js",
@@ -32,5 +40,8 @@
32
40
  ],
33
41
  "engines": {
34
42
  "node": ">=18.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "jest": "^30.2.0"
35
46
  }
36
47
  }