@testomatio/mcp 1.0.3 → 1.0.5

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 +169 -5
  2. package/index.js +284 -14
  3. package/package.json +14 -3
package/README.md CHANGED
@@ -112,13 +112,79 @@ Then add this to your Cursor MCP settings:
112
112
  #### Tests
113
113
  * `get_tests` – Get all tests (params: `plan`, `query`, `state`, `suite_id`, `tag`, `labels`) — api: GET `/tests`
114
114
  * `search_tests` – Search tests (params: `query`, `tql`, `labels`, `state`, `priority`, `filter`, `page`) — api: GET `/tests`
115
- * `create_test` – Create a new test (params: `suite_id`, `title`, `description`, `code`, `file`, `state`, `tags`, `jira_issues`, `assigned_to`, `labels_ids`) — api: POST `/tests`
116
- * `update_test` – Update an existing test (params: `test_id`, `suite_id`, `title`, `description`, `code`, `file`, `state`, `tags`, `jira_issues`, `assigned_to`, `labels_ids`) — api: PUT `/tests/{test_id}`
115
+ * `create_test` – Create a new test (params: `suite_id`, `title`, `description`, `code`, `file`, `state`, `tags`, `jira_issues`, `assigned_to`, `labels_ids`, `fields`) — api: POST `/tests`
116
+ * `update_test` – Update an existing test (params: `test_id`, `suite_id`, `title`, `description`, `code`, `file`, `state`, `tags`, `jira_issues`, `assigned_to`, `labels_ids`, `fields`) — api: PUT `/tests/{test_id}`
117
117
 
118
118
  #### Test Suites
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`, `fields`) — api: POST `/suites`
123
+ * `create_folder` – Create a new folder (params: `title`, `description`, `parent_id`, `fields`) — 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`
127
+
128
+ ### Custom Fields and Labels
129
+
130
+ The MCP server provides two distinct ways to assign values to tests, suites, and folders:
131
+
132
+ #### 1. Using `labels_ids` with label:value syntax
133
+ ```javascript
134
+ {
135
+ "labels_ids": ["priority:high", "severity:critical", "type:regression"]
136
+ }
137
+ ```
138
+ - Direct label assignment with values using `label:value` format
139
+ - Good for simple label assignments
140
+ - Works with existing Testomatio labels
141
+
142
+ #### 2. Using `fields` parameter (structured custom fields)
143
+ ```javascript
144
+ {
145
+ "fields": {
146
+ "priority": "high",
147
+ "severity": "critical",
148
+ "risk_score": "8.5",
149
+ "team": "backend"
150
+ }
151
+ }
152
+ ```
153
+ - Structured way to set custom fields
154
+ - Cleaner syntax for AI assistants
155
+ - Supports any custom field defined in your Testomatio project
156
+ - Maps to Testomatio's custom-fields API
157
+
158
+ **Available for:**
159
+ - `create_test` and `update_test` - Test custom fields
160
+ - `create_suite` - Suite custom fields
161
+ - `create_folder` - Folder custom fields
162
+
163
+ #### Example Usage
164
+ ```javascript
165
+ // Create a test with custom fields
166
+ {
167
+ "tool": "create_test",
168
+ "arguments": {
169
+ "suite_id": "123",
170
+ "title": "Login Test",
171
+ "fields": {
172
+ "priority": "high",
173
+ "severity": "critical",
174
+ "team": "backend"
175
+ }
176
+ }
177
+ }
178
+
179
+ // Update a test with label:value syntax
180
+ {
181
+ "tool": "update_test",
182
+ "arguments": {
183
+ "test_id": "456",
184
+ "labels_ids": ["priority:high", "severity:critical"]
185
+ }
186
+ }
187
+ ```
122
188
 
123
189
  #### Test Runs
124
190
  * `get_runs` – List all runs (no params) — api: GET `/runs`
@@ -141,6 +207,12 @@ Once configured, you can ask your AI assistant questions like:
141
207
  - "Get all test plans for this project"
142
208
  - "Create a new test called 'Login validation' in suite suite-123"
143
209
  - "Update test test-456 to change its description and add @regression tag"
210
+ - "Create a test with custom fields: priority='high', severity='critical', team='backend'"
211
+ - "Update test test-789 to set custom fields for risk score and assigned team"
212
+ - "Create a new suite called 'Authentication Tests' with description 'All login and signup related tests'"
213
+ - "Create a suite with custom fields for team ownership and priority level"
214
+ - "Create a folder called 'API Tests' to organize API-related test suites with custom fields"
215
+ - "Create a label called 'Severity' with color '#ffe9ad' and predefined values like 'Blocker', 'Critical', 'Major', 'Minor', 'Normal', 'Trivial'"
144
216
 
145
217
  ## Query Patterns
146
218
 
@@ -160,6 +232,31 @@ These queries allow creating and updating tests:
160
232
  - **"Create a new test called 'Login validation' in suite suite-123"** → `create_test` tool with `title: "Login validation"`, `suite_id: "suite-123"`
161
233
  - **"Update test test-456 to change its description"** → `update_test` tool with `test_id: "test-456"`, `description: "new description"`
162
234
  - **"Create an automated test with @smoke tag"** → `create_test` tool with `state: "automated"`, `tags: ["smoke"]`
235
+ - **"Create a test with custom fields: priority='high', severity='critical'"** → `create_test` tool with `title: "Test Title"`, `suite_id: "suite-123"`, `fields: { "priority": "high", "severity": "critical" }`
236
+ - **"Update test test-789 to set custom fields for risk score and team"** → `update_test` tool with `test_id: "test-789"`, `fields: { "risk_score": "8.5", "team": "backend" }`
237
+
238
+ ### Suite and Folder Management Queries
239
+
240
+ These queries help organize your test structure:
241
+
242
+ - **"Create a new suite called 'Authentication Tests'"** → `create_suite` tool with `title: "Authentication Tests"`
243
+ - **"Create a suite for login tests with description"** → `create_suite` tool with `title: "Login Tests"`, `description: "All login related test cases"`
244
+ - **"Create a suite with custom fields for team ownership and priority level"** → `create_suite` tool with `title: "Backend Tests"`, `fields: { "team": "backend", "priority": "high" }`
245
+ - **"Create a folder called 'API Tests' under parent suite-123"** → `create_folder` tool with `title: "API Tests"`, `parent_id: "suite-123"`
246
+ - **"Create a folder with custom fields for team and project"** → `create_folder` tool with `title: "Integration Tests"`, `fields: { "team": "qa", "project": "mobile-app" }`
247
+ - **"Create a test suite for payment features"** → `create_suite` tool with `title: "Payment Features", description: "Tests covering payment processing"`
248
+ - **"Create a folder to organize integration tests"** → `create_folder` tool with `title: "Integration Tests"`
249
+
250
+ **Note**: Suites can only contain other suites, while folders can contain both suites and folders (but no tests).
251
+
252
+ ### Label Creation Queries
253
+
254
+ These queries help create custom labels for better test categorization:
255
+
256
+ - **"Create a label called 'Severity' with red color"** → `create_label` tool with `title: "Severity"`, `color: "#ff6b6b"`
257
+ - **"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" }`
258
+ - **"Create a simple label for test types"** → `create_label` tool with `title: "Test Type"`, `scope: ["tests", "suites"]`
259
+ - **"Create a label visible in test lists"** → `create_label` tool with `title: "Category"`, `visibility: ["list"]`
163
260
 
164
261
  ### Specific Item Queries
165
262
 
@@ -174,9 +271,8 @@ These queries target specific entities by ID:
174
271
  These queries use advanced filtering capabilities:
175
272
 
176
273
  - **"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
274
  - **"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'"`
275
+ - **"List tests tagged @critical or labelled 'ux' with critical severity"** → `search_tests` tool with `tql: "tag == 'critical' or label == 'ux' and severity == 'critical'"`
180
276
  - **"Find tests linked to JIRA-123"** → `search_tests` tool with `tql: jira == 'BDCP-2'`
181
277
 
182
278
  ### Advanced Query Syntax
@@ -187,7 +283,7 @@ The `search_tests` tool supports TQL for complex filtering:
187
283
 
188
284
  ```
189
285
  "tag == 'smoke' and state == 'manual'"
190
- "priority == 'high' or label == 'ux'"
286
+ "severity == 'critical' or label == 'ux'"
191
287
  ```
192
288
 
193
289
  #### Tag-Based Searches
@@ -247,6 +343,74 @@ For detailed information about the underlying Testomat.io API, refer to the [Tes
247
343
 
248
344
  Contributions are welcome! Please feel free to submit a Pull Request.
249
345
 
346
+ ### Development Setup
347
+
348
+ ```bash
349
+ # Clone the repository
350
+ git clone https://github.com/testomatio/mcp.git
351
+ cd mcp
352
+
353
+ # Install dependencies
354
+ npm install
355
+
356
+ # Run unit tests
357
+ npm test
358
+
359
+ # Run integration tests (requires environment variables)
360
+ npm run test:integration
361
+
362
+ # Run all tests
363
+ npm run test:all
364
+ ```
365
+
366
+ ### Testing
367
+
368
+ The project includes comprehensive test coverage:
369
+
370
+ - **Unit Tests**: Fast tests with mocked dependencies
371
+ - **Integration Tests**: Real API tests against Testomat.io
372
+
373
+ #### Running Tests Locally
374
+
375
+ ```bash
376
+ # Unit tests only
377
+ npm run test:unit
378
+
379
+ # Integration tests (requires .env file)
380
+ npm run test:integration
381
+
382
+ # With coverage
383
+ npm run test:coverage
384
+ npm run test:coverage:integration
385
+ ```
386
+
387
+ #### Environment Setup for Integration Tests
388
+
389
+ Create a `.env` file:
390
+
391
+ ```bash
392
+ TESTOMATIO_API_TOKEN=testomat_your_token_here
393
+ TESTOMATIO_PROJECT_ID=your_project_id
394
+ TESTOMATIO_BASE_URL=https://app.testomat.io # optional
395
+ ```
396
+
397
+ ### CI/CD
398
+
399
+ This project uses GitHub Actions for continuous integration:
400
+
401
+ - ✅ **Unit Tests**: Run on every push/PR across Node.js 18, 20, 22
402
+ - ✅ **Integration Tests**: Run daily and on main branch merges
403
+ - ✅ **Coverage Reports**: Automatic upload to Codecov
404
+ - ✅ **Security**: Secrets management for API credentials
405
+
406
+
407
+ ### Code Quality
408
+
409
+ - Follow existing code style patterns
410
+ - Add tests for new functionality
411
+ - Update documentation when needed
412
+ - Ensure all tests pass before submitting PRs
413
+
250
414
  ## License
251
415
 
252
416
  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();
@@ -317,7 +318,14 @@ class TestomatioMCPServer {
317
318
  labels_ids: {
318
319
  type: 'array',
319
320
  items: { type: 'string' },
320
- description: 'Slugs of labels to assign to the test',
321
+ description: 'Slugs of labels to assign to the test. Supports label:value format (e.g., ["priority:high", "severity:critical"])',
322
+ },
323
+ fields: {
324
+ type: 'object',
325
+ description: 'Set custom fields for the test. Object with field names as keys and values as properties (e.g., {"priority": "high", "severity": "critical"})',
326
+ additionalProperties: {
327
+ type: 'string'
328
+ },
321
329
  },
322
330
  },
323
331
  required: ['suite_id', 'title'],
@@ -358,6 +366,11 @@ class TestomatioMCPServer {
358
366
  enum: ['manual', 'automated'],
359
367
  description: 'State of the test',
360
368
  },
369
+ priority: {
370
+ type: 'string',
371
+ enum: ['low', 'normal', 'high', 'critical'],
372
+ description: 'Priority level of the test',
373
+ },
361
374
  tags: {
362
375
  type: 'array',
363
376
  items: { type: 'string' },
@@ -375,12 +388,129 @@ class TestomatioMCPServer {
375
388
  labels_ids: {
376
389
  type: 'array',
377
390
  items: { type: 'string' },
378
- description: 'Slugs of labels to assign to the test',
391
+ description: 'Slugs of labels to assign to the test. Supports label:value format (e.g., ["priority:high", "severity:critical"])',
392
+ },
393
+ fields: {
394
+ type: 'object',
395
+ description: 'Set custom fields for the test. Object with field names as keys and values as properties (e.g., {"priority": "high", "severity": "critical"})',
396
+ additionalProperties: {
397
+ type: 'string'
398
+ },
379
399
  },
380
400
  },
381
401
  required: ['test_id'],
382
402
  },
383
403
  },
404
+ {
405
+ name: 'create_suite',
406
+ description: 'Create a new suite. Suites can only contain other suites (no tests or folders)',
407
+ inputSchema: {
408
+ type: 'object',
409
+ properties: {
410
+ title: {
411
+ type: 'string',
412
+ description: 'Suite title',
413
+ },
414
+ description: {
415
+ type: 'string',
416
+ description: 'Suite description',
417
+ },
418
+ parent_id: {
419
+ type: 'string',
420
+ description: 'Parent suite ID to create this suite under',
421
+ },
422
+ fields: {
423
+ type: 'object',
424
+ description: 'Set custom fields for the suite. Object with field names as keys and values as properties (e.g., {"priority": "high", "team": "backend"})',
425
+ additionalProperties: {
426
+ type: 'string'
427
+ },
428
+ },
429
+ },
430
+ required: ['title'],
431
+ },
432
+ },
433
+ {
434
+ name: 'create_folder',
435
+ description: 'Create a new folder. Folders can contain suites and folders (but no tests)',
436
+ inputSchema: {
437
+ type: 'object',
438
+ properties: {
439
+ title: {
440
+ type: 'string',
441
+ description: 'Folder title',
442
+ },
443
+ description: {
444
+ type: 'string',
445
+ description: 'Folder description',
446
+ },
447
+ parent_id: {
448
+ type: 'string',
449
+ description: 'Parent folder or suite ID to create this folder under',
450
+ },
451
+ fields: {
452
+ type: 'object',
453
+ description: 'Set custom fields for the folder. Object with field names as keys and values as properties (e.g., {"priority": "high", "team": "backend"})',
454
+ additionalProperties: {
455
+ type: 'string'
456
+ },
457
+ },
458
+ },
459
+ required: ['title'],
460
+ },
461
+ },
462
+ {
463
+ name: 'create_label',
464
+ description: 'Create a new label with optional custom field configuration. Labels can be used to tag and categorize tests and suites',
465
+ inputSchema: {
466
+ type: 'object',
467
+ properties: {
468
+ title: {
469
+ type: 'string',
470
+ description: 'Label title (e.g., "Severity", "Priority", "Type")',
471
+ },
472
+ color: {
473
+ type: 'string',
474
+ description: 'Label color in hex format (e.g., "#ffe9ad")',
475
+ },
476
+ scope: {
477
+ type: 'array',
478
+ items: {
479
+ type: 'string',
480
+ enum: ['tests', 'suites']
481
+ },
482
+ description: 'Where this label can be used (e.g., ["tests", "suites"])',
483
+ },
484
+ visibility: {
485
+ type: 'array',
486
+ items: {
487
+ type: 'string'
488
+ },
489
+ description: 'Where the label is visible (e.g., ["list"])',
490
+ },
491
+ field: {
492
+ type: 'object',
493
+ description: 'Custom field configuration for labels with predefined values',
494
+ properties: {
495
+ type: {
496
+ type: 'string',
497
+ description: 'Field type (e.g., "list", "string", "number")',
498
+ },
499
+ short: {
500
+ type: 'boolean',
501
+ description: 'Whether to display short version',
502
+ },
503
+ value: {
504
+ type: 'string',
505
+ description: 'Predefined values for the field (newline separated for list type)',
506
+ },
507
+ },
508
+ required: ['type'],
509
+ },
510
+ },
511
+ required: ['title'],
512
+ },
513
+ },
384
514
  ],
385
515
  };
386
516
  });
@@ -414,6 +544,12 @@ class TestomatioMCPServer {
414
544
  return await this.createTest(args);
415
545
  case 'update_test':
416
546
  return await this.updateTest(args);
547
+ case 'create_suite':
548
+ return await this.createSuite(args);
549
+ case 'create_folder':
550
+ return await this.createFolder(args);
551
+ case 'create_label':
552
+ return await this.createLabel(args);
417
553
  default:
418
554
  throw new Error(`Unknown tool: ${name}`);
419
555
  }
@@ -914,14 +1050,19 @@ class TestomatioMCPServer {
914
1050
  }
915
1051
 
916
1052
  async createTest(args) {
917
- const { suite_id, labels_ids, ...attributes } = args;
1053
+ const { suite_id, labels_ids, fields, ...attributes } = args;
1054
+
1055
+ // Handle fields parameter for custom fields
918
1056
  const requestData = {
919
1057
  data: {
920
1058
  type: 'tests',
921
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
1059
+ attributes: {
1060
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1061
+ ...(fields && { 'custom-fields': fields })
1062
+ }
922
1063
  }
923
1064
  };
924
-
1065
+
925
1066
  if (suite_id) {
926
1067
  requestData.data.relationships = {
927
1068
  suite: {
@@ -932,7 +1073,7 @@ class TestomatioMCPServer {
932
1073
  }
933
1074
  };
934
1075
  }
935
-
1076
+
936
1077
  if (labels_ids) requestData.labels_ids = labels_ids;
937
1078
 
938
1079
  const data = await this.makePostRequest('/tests', requestData);
@@ -952,14 +1093,19 @@ class TestomatioMCPServer {
952
1093
  }
953
1094
 
954
1095
  async updateTest(args) {
955
- const { test_id, suite_id, labels_ids, ...attributes } = args;
1096
+ const { test_id, suite_id, labels_ids, fields, ...attributes } = args;
1097
+
1098
+ // Handle fields parameter for custom fields
956
1099
  const requestData = {
957
1100
  data: {
958
1101
  type: 'tests',
959
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
1102
+ attributes: {
1103
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1104
+ ...(fields && { 'custom-fields': fields })
1105
+ }
960
1106
  }
961
1107
  };
962
-
1108
+
963
1109
  if (suite_id) {
964
1110
  requestData.data.relationships = {
965
1111
  suite: {
@@ -970,7 +1116,7 @@ class TestomatioMCPServer {
970
1116
  }
971
1117
  };
972
1118
  }
973
-
1119
+
974
1120
  if (labels_ids) requestData.labels_ids = labels_ids;
975
1121
 
976
1122
  const data = await this.makePutRequest(`/tests/${test_id}`, requestData);
@@ -989,6 +1135,124 @@ class TestomatioMCPServer {
989
1135
  };
990
1136
  }
991
1137
 
1138
+ async createSuite(args) {
1139
+ const { parent_id, fields, ...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': 'file',
1146
+ ...(fields && { 'custom-fields': fields })
1147
+ }
1148
+ }
1149
+ };
1150
+
1151
+ if (parent_id) {
1152
+ requestData.data.relationships = {
1153
+ parent: {
1154
+ data: {
1155
+ type: 'suites',
1156
+ id: parent_id
1157
+ }
1158
+ }
1159
+ };
1160
+ }
1161
+
1162
+ const data = await this.makePostRequest('/suites', requestData);
1163
+ const formattedSuite = this.formatModel(data.data, 'suite', [
1164
+ 'title', 'description', 'test-count', 'is-root', 'file-type'
1165
+ ]);
1166
+
1167
+ return {
1168
+ content: [
1169
+ {
1170
+ type: 'text',
1171
+ text: `Successfully created suite:\n\n${formattedSuite}`,
1172
+ },
1173
+ ],
1174
+ };
1175
+ }
1176
+
1177
+ async createFolder(args) {
1178
+ const { parent_id, fields, ...attributes } = args;
1179
+ const requestData = {
1180
+ data: {
1181
+ type: 'suites',
1182
+ attributes: {
1183
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1184
+ 'file-type': 'folder',
1185
+ ...(fields && { 'custom-fields': fields })
1186
+ }
1187
+ }
1188
+ };
1189
+
1190
+ if (parent_id) {
1191
+ requestData.data.relationships = {
1192
+ parent: {
1193
+ data: {
1194
+ type: 'suites',
1195
+ id: parent_id
1196
+ }
1197
+ }
1198
+ };
1199
+ }
1200
+
1201
+ const data = await this.makePostRequest('/suites', requestData);
1202
+ const formattedSuite = this.formatModel(data.data, 'suite', [
1203
+ 'title', 'description', 'test-count', 'is-root', 'file-type'
1204
+ ]);
1205
+
1206
+ return {
1207
+ content: [
1208
+ {
1209
+ type: 'text',
1210
+ text: `Successfully created folder:\n\n${formattedSuite}`,
1211
+ },
1212
+ ],
1213
+ };
1214
+ }
1215
+
1216
+ async createLabel(args) {
1217
+ const { title, color, scope, visibility, field, ...otherAttributes } = args;
1218
+
1219
+ const requestData = {
1220
+ data: {
1221
+ type: 'labels',
1222
+ attributes: {
1223
+ ...Object.fromEntries(Object.entries(otherAttributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1224
+ title,
1225
+ color,
1226
+ scope,
1227
+ visibility
1228
+ }
1229
+ }
1230
+ };
1231
+
1232
+ // Add field configuration if provided
1233
+ if (field) {
1234
+ requestData.data.attributes.field = {
1235
+ type: field.type,
1236
+ ...(field.short !== undefined && { short: field.short }),
1237
+ ...(field.value && { value: field.value })
1238
+ };
1239
+ }
1240
+
1241
+ const data = await this.makePostRequest('/labels', requestData);
1242
+ const formattedLabel = this.formatModel(data.data, 'label', [
1243
+ 'title', 'color', 'scope', 'visibility', 'field'
1244
+ ]);
1245
+
1246
+ return {
1247
+ content: [
1248
+ {
1249
+ type: 'text',
1250
+ text: `Successfully created label:\n\n${formattedLabel}`,
1251
+ },
1252
+ ],
1253
+ };
1254
+ }
1255
+
992
1256
  async run() {
993
1257
  // Test authentication on startup
994
1258
  try {
@@ -1005,6 +1269,9 @@ class TestomatioMCPServer {
1005
1269
  }
1006
1270
  }
1007
1271
 
1272
+ // Export the class for testing
1273
+ export { TestomatioMCPServer };
1274
+
1008
1275
  // Parse command line arguments using commander
1009
1276
  function parseArgs() {
1010
1277
  program
@@ -1019,7 +1286,7 @@ function parseArgs() {
1019
1286
  const options = program.opts();
1020
1287
 
1021
1288
  const token = options.token || process.env.TESTOMATIO_API_TOKEN;
1022
- const projectId = options.project;
1289
+ const projectId = options.project || process.env.TESTOMATIO_PROJECT_ID;
1023
1290
  const baseUrl = options.baseUrl || process.env.TESTOMATIO_BASE_URL || 'https://app.testomat.io';
1024
1291
 
1025
1292
  if (!token) {
@@ -1028,7 +1295,7 @@ function parseArgs() {
1028
1295
  }
1029
1296
 
1030
1297
  if (!projectId) {
1031
- console.error('Error: Project ID is required. Use --project <project_id>');
1298
+ console.error('Error: Project ID is required. Use --project <project_id> or set TESTOMATIO_PROJECT_ID environment variable');
1032
1299
  process.exit(1);
1033
1300
  }
1034
1301
 
@@ -1047,4 +1314,7 @@ async function main() {
1047
1314
  }
1048
1315
  }
1049
1316
 
1050
- main().catch(console.error);
1317
+ // Only run main() if this file is executed directly (not imported)
1318
+ if (import.meta.url === `file://${process.argv[1]}`) {
1319
+ main().catch(console.error);
1320
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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
  }