@testomatio/mcp 1.0.9 → 1.0.12

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 +30 -12
  2. package/index.js +195 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A Model Context Protocol (MCP) server for Testomat.io API integration with AI assistants like Cursor.
4
4
 
5
+ <img width="1920" height="1085" alt="image" src="https://github.com/user-attachments/assets/cf823e8b-1305-4ed2-a7c5-712efec12ceb" />
6
+
5
7
  ## Installation
6
8
 
7
9
  ### Prerequisites
@@ -62,6 +64,17 @@ Your project ID can be found in the URL when you're viewing your project:
62
64
  https://app.testomat.io/projects/YOUR_PROJECT_ID
63
65
  ```
64
66
 
67
+ ### ID Formats
68
+
69
+ Testomatio uses specific ID formats for different resources:
70
+
71
+ - **Test IDs**: Start with `T` prefix followed by 7 alphanumeric characters (e.g., `Ta1b2c3d4`)
72
+ - **Suite IDs**: Start with `S` prefix followed by 7 alphanumeric characters (e.g., `Sx9y8z7w6`)
73
+ - **Total length**: 8 characters including the prefix
74
+ - **Format**: `[Prefix][7 alphanumeric characters]`
75
+
76
+ When working with tools that require specific IDs, ensure you use the correct format with the appropriate prefix.
77
+
65
78
  ## Integration with Cursor
66
79
 
67
80
  To use this MCP server with Cursor, add the following configuration to your Cursor settings:
@@ -111,6 +124,7 @@ Then add this to your Cursor MCP settings:
111
124
 
112
125
  #### Tests
113
126
  * `get_tests` – Get all tests (params: `plan`, `query`, `state`, `suite_id`, `tag`, `labels`) — api: GET `/tests`
127
+ * `get_test` – Get a specific test by ID with all information including labels, tags, and metadata (params: `test_id`) — api: GET `/tests/{test_id}`
114
128
  * `search_tests` – Search tests (params: `query`, `tql`, `labels`, `state`, `priority`, `filter`, `page`) — api: GET `/tests`
115
129
  * `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
130
  * `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}`
@@ -123,7 +137,9 @@ Then add this to your Cursor MCP settings:
123
137
  * `create_folder` – Create a new folder (params: `title`, `description`, `parent_id`, `fields`) — api: POST `/suites`
124
138
 
125
139
  #### Labels
126
- * `create_label` – Create a new label with optional custom field (params: `title`, `color`, `scope`, `visibility`, `field`) — api: POST `/labels`
140
+ * `get_labels` – Get all available labels with their IDs and configurations
141
+ * `create_label` – Create a new label with optional custom field
142
+ * `unlink_label` – Remove a label from a test or suite
127
143
 
128
144
  ### Custom Fields and Labels
129
145
 
@@ -200,15 +216,16 @@ The MCP server provides two distinct ways to assign values to tests, suites, and
200
216
  Once configured, you can ask your AI assistant questions like:
201
217
 
202
218
  - "Show me all the tests in the project"
203
- - "Get the test runs for test ID abc123"
219
+ - "Get details for test ID Ta1b2c3d4"
220
+ - "Get the test runs for test ID Ta1b2c3d4"
204
221
  - "What are the root suites in this project?"
205
222
  - "Show me details for test run xyz789"
206
223
  - "List all automated tests with the @smoke tag"
207
224
  - "Get all test plans for this project"
208
- - "Create a new test called 'Login validation' in suite suite-123"
209
- - "Update test test-456 to change its description and add @regression tag"
225
+ - "Create a new test called 'Login validation' in suite Sx9y8z7w6"
226
+ - "Update test Ta1b2c3d4 to change its description and add @regression tag"
210
227
  - "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"
228
+ - "Update test Tb2c3d4e5 to set custom fields for risk score and assigned team"
212
229
  - "Create a new suite called 'Authentication Tests' with description 'All login and signup related tests'"
213
230
  - "Create a suite with custom fields for team ownership and priority level"
214
231
  - "Create a folder called 'API Tests' to organize API-related test suites with custom fields"
@@ -229,11 +246,11 @@ These queries retrieve general information without specific filtering:
229
246
 
230
247
  These queries allow creating and updating tests:
231
248
 
232
- - **"Create a new test called 'Login validation' in suite suite-123"** → `create_test` tool with `title: "Login validation"`, `suite_id: "suite-123"`
233
- - **"Update test test-456 to change its description"** → `update_test` tool with `test_id: "test-456"`, `description: "new description"`
249
+ - **"Create a new test called 'Login validation' in suite Sx9y8z7w6"** → `create_test` tool with `title: "Login validation"`, `suite_id: "Sx9y8z7w6"`
250
+ - **"Update test Ta1b2c3d4 to change its description"** → `update_test` tool with `test_id: "Ta1b2c3d4"`, `description: "new description"`
234
251
  - **"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" }`
252
+ - **"Create a test with custom fields: priority='high', severity='critical'"** → `create_test` tool with `title: "Test Title"`, `suite_id: "Sx9y8z7w6"`, `fields: { "priority": "high", "severity": "critical" }`
253
+ - **"Update test Tb2c3d4e5 to set custom fields for risk score and team"** → `update_test` tool with `test_id: "Tb2c3d4e5"`, `fields: { "risk_score": "8.5", "team": "backend" }`
237
254
 
238
255
  ### Suite and Folder Management Queries
239
256
 
@@ -242,7 +259,7 @@ These queries help organize your test structure:
242
259
  - **"Create a new suite called 'Authentication Tests'"** → `create_suite` tool with `title: "Authentication Tests"`
243
260
  - **"Create a suite for login tests with description"** → `create_suite` tool with `title: "Login Tests"`, `description: "All login related test cases"`
244
261
  - **"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"`
262
+ - **"Create a folder called 'API Tests' under parent suite Sx9y8z7w6"** → `create_folder` tool with `title: "API Tests"`, `parent_id: "Sx9y8z7w6"`
246
263
  - **"Create a folder with custom fields for team and project"** → `create_folder` tool with `title: "Integration Tests"`, `fields: { "team": "qa", "project": "mobile-app" }`
247
264
  - **"Create a test suite for payment features"** → `create_suite` tool with `title: "Payment Features", description: "Tests covering payment processing"`
248
265
  - **"Create a folder to organize integration tests"** → `create_folder` tool with `title: "Integration Tests"`
@@ -262,9 +279,10 @@ These queries help create custom labels for better test categorization:
262
279
 
263
280
  These queries target specific entities by ID:
264
281
 
265
- - **"Get test runs for test ID abc123"** → `get_testruns` tool with `test_id: "abc123"`
282
+ - **"Get details for test ID Ta1b2c3d4"** → `get_test` tool with `test_id: "Ta1b2c3d4"`
283
+ - **"Get test runs for test ID Ta1b2c3d4"** → `get_testruns` tool with `test_id: "Ta1b2c3d4"`
266
284
  - **"Show me details for test run xyz789"** → `get_run` tool with `run_id: "xyz789"`
267
- - **"Get suite details for suite-456"** → `get_suite` tool with `suite_id: "suite-456"`
285
+ - **"Get suite details for suite Sx9y8z7w6"** → `get_suite` tool with `suite_id: "Sx9y8z7w6"`
268
286
 
269
287
  ### Search and Filter Queries
270
288
 
package/index.js CHANGED
@@ -116,6 +116,20 @@ class TestomatioMCPServer {
116
116
  },
117
117
  },
118
118
  },
119
+ {
120
+ name: 'get_test',
121
+ description: 'Get a specific test by its ID with all information including labels, tags, and metadata',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ test_id: {
126
+ type: 'string',
127
+ description: 'The ID of the test to retrieve',
128
+ },
129
+ },
130
+ required: ['test_id'],
131
+ },
132
+ },
119
133
  {
120
134
  name: 'search_tests',
121
135
  description: 'Search tests by keywords, tags, labels, TQL queries, and other filters',
@@ -303,7 +317,7 @@ class TestomatioMCPServer {
303
317
  },
304
318
  title: {
305
319
  type: 'string',
306
- description: 'Test title',
320
+ description: 'Test title. @tags in the title (e.g., "@smoke test") will be automatically extracted as tags',
307
321
  },
308
322
  description: {
309
323
  type: 'string',
@@ -325,7 +339,7 @@ class TestomatioMCPServer {
325
339
  tags: {
326
340
  type: 'array',
327
341
  items: { type: 'string' },
328
- description: 'List of tags for the test',
342
+ description: 'List of @tags for the test. Tags are automatically extracted from @ mentions in the title (e.g., @smoke, @regression). Can also be provided as an array of tag names (without @ prefix).',
329
343
  },
330
344
  jira_issues: {
331
345
  type: 'array',
@@ -368,7 +382,7 @@ class TestomatioMCPServer {
368
382
  },
369
383
  title: {
370
384
  type: 'string',
371
- description: 'Test title',
385
+ description: 'Test title. @tags in the title (e.g., "@smoke test") will be automatically extracted as tags',
372
386
  },
373
387
  description: {
374
388
  type: 'string',
@@ -395,7 +409,7 @@ class TestomatioMCPServer {
395
409
  tags: {
396
410
  type: 'array',
397
411
  items: { type: 'string' },
398
- description: 'List of tags for the test',
412
+ description: 'List of @tags for the test. Tags are automatically extracted from @ mentions in the title (e.g., @smoke, @regression). Can also be provided as an array of tag names (without @ prefix).',
399
413
  },
400
414
  jira_issues: {
401
415
  type: 'array',
@@ -501,6 +515,36 @@ class TestomatioMCPServer {
501
515
  },
502
516
  },
503
517
  },
518
+ {
519
+ name: 'unlink_label',
520
+ description: 'Remove a label from a test or suite. Can remove specific label values or all instances of the label',
521
+ inputSchema: {
522
+ type: 'object',
523
+ properties: {
524
+ label_id: {
525
+ type: 'string',
526
+ description: 'Label ID to remove (e.g., "priority", "severity")',
527
+ },
528
+ test_id: {
529
+ type: 'string',
530
+ description: 'Test ID to remove label from (use either test_id or suite_id)',
531
+ },
532
+ suite_id: {
533
+ type: 'string',
534
+ description: 'Suite ID to remove label from (use either test_id or suite_id)',
535
+ },
536
+ value: {
537
+ type: 'string',
538
+ description: 'Specific label value to remove (e.g., "high", "critical"). If omitted, all instances are removed',
539
+ },
540
+ },
541
+ required: ['label_id'],
542
+ oneOf: [
543
+ { required: ['test_id'] },
544
+ { required: ['suite_id'] }
545
+ ],
546
+ },
547
+ },
504
548
  {
505
549
  name: 'create_label',
506
550
  description: 'Create a new label with optional custom field configuration. Labels can be used to tag and categorize tests and suites',
@@ -564,6 +608,8 @@ class TestomatioMCPServer {
564
608
  switch (name) {
565
609
  case 'get_tests':
566
610
  return await this.getTests(args);
611
+ case 'get_test':
612
+ return await this.getTest(args.test_id);
567
613
  case 'search_tests':
568
614
  return await this.searchTests(args);
569
615
  case 'search_suites':
@@ -584,6 +630,8 @@ class TestomatioMCPServer {
584
630
  return await this.getPlan(args.plan_id);
585
631
  case 'get_labels':
586
632
  return await this.getLabels(args);
633
+ case 'unlink_label':
634
+ return await this.unlinkLabel(args);
587
635
  case 'create_test':
588
636
  return await this.createTest(args);
589
637
  case 'update_test':
@@ -719,19 +767,22 @@ class TestomatioMCPServer {
719
767
  if (fieldName === 'tags') {
720
768
  return value.map(tag => `<tag>${this.escapeXml(tag)}</tag>`).join('');
721
769
  }
722
- if (fieldName === 'labels') {
723
- return value.map(label => `<label>${this.escapeXml(label)}</label>`).join('');
724
- }
725
770
  if (fieldName === 'tests-ids') {
726
771
  return value.map(id => `<test_id>${id}</test_id>`).join('');
727
772
  }
728
- // Default array handling
729
- return value.map(item => `<item>${this.escapeXml(item)}</item>`).join('');
773
+ // Default array handling - use field name as tag (e.g., labels -> <label>)
774
+ const singularFieldName = fieldName.endsWith('s') ? fieldName.slice(0, -1) : fieldName;
775
+ return value.map(item => {
776
+ if (typeof item === 'object' && item !== null) {
777
+ return `<${singularFieldName}>${JSON.stringify(item)}</${singularFieldName}>`;
778
+ }
779
+ return `<${singularFieldName}>${this.escapeXml(item)}</${singularFieldName}>`;
780
+ }).join('');
730
781
  }
731
782
 
732
- // Handle nested objects
783
+ // Handle nested objects - stringify them without escaping
733
784
  if (typeof value === 'object') {
734
- return this.formatNestedObject(value, fieldName);
785
+ return JSON.stringify(value);
735
786
  }
736
787
 
737
788
  // Handle strings that need escaping
@@ -796,11 +847,16 @@ class TestomatioMCPServer {
796
847
 
797
848
  async getTests(filters = {}) {
798
849
  const params = this.buildSearchParams(filters);
850
+ // Add labels=true and detail=true for comprehensive test information
851
+ params.labels = 'true';
852
+ params.detail = 'true';
853
+
799
854
  const data = await this.makeRequest('/tests', params);
800
855
  const formattedTests = data.data.map(test =>
801
856
  this.formatModel(test, 'test', [
802
- 'title', 'description', 'code', 'priority',
803
- 'state', 'suite-id', 'tags', 'file'
857
+ 'title', 'description', 'code', 'priority', 'state',
858
+ 'suite-id', 'tags', 'file', 'jira-issues', 'assigned-to',
859
+ 'created-at', 'updated-at', 'labels'
804
860
  ])
805
861
  ).join('\n\n');
806
862
 
@@ -814,6 +870,25 @@ class TestomatioMCPServer {
814
870
  };
815
871
  }
816
872
 
873
+ async getTest(testId) {
874
+ // Add labels=true and detail=true for comprehensive test information
875
+ const data = await this.makeRequest(`/tests/${testId}`, { labels: 'true', detail: 'true' });
876
+ const formattedTest = this.formatModel(data.data, 'test', [
877
+ 'title', 'description', 'code', 'priority', 'state',
878
+ 'suite-id', 'tags', 'file', 'jira-issues', 'assigned-to',
879
+ 'created-at', 'updated-at', 'labels'
880
+ ]);
881
+
882
+ return {
883
+ content: [
884
+ {
885
+ type: 'text',
886
+ text: `Test ${testId}:\n\n${formattedTest}`,
887
+ },
888
+ ],
889
+ };
890
+ }
891
+
817
892
  buildSearchParams(filters = {}) {
818
893
  const params = {};
819
894
 
@@ -861,12 +936,17 @@ class TestomatioMCPServer {
861
936
 
862
937
  async searchTests(filters = {}) {
863
938
  const params = this.buildSearchParams(filters);
939
+ // Add labels=true and detail=true for comprehensive test information
940
+ params.labels = 'true';
941
+ params.detail = 'true';
942
+
864
943
  const data = await this.makeRequest('/tests', params);
865
944
 
866
945
  const formattedTests = data.data.map(test =>
867
946
  this.formatModel(test, 'test', [
868
- 'title', 'description', 'code', 'priority',
869
- 'state', 'suite-id', 'tags', 'file'
947
+ 'title', 'description', 'code', 'priority', 'state',
948
+ 'suite-id', 'tags', 'file', 'jira-issues', 'assigned-to',
949
+ 'created-at', 'updated-at', 'labels'
870
950
  ])
871
951
  ).join('\n\n');
872
952
 
@@ -1130,9 +1210,56 @@ class TestomatioMCPServer {
1130
1210
  };
1131
1211
  }
1132
1212
 
1213
+ extractTagsFromTitle(title) {
1214
+ if (!title) return [];
1215
+
1216
+ // Find all @tags in the title
1217
+ const tagMatches = title.match(/@([a-zA-Z0-9_\-]+)/g);
1218
+
1219
+ if (!tagMatches) return [];
1220
+
1221
+ // Remove @ prefix and deduplicate
1222
+ const tags = tagMatches
1223
+ .map(tag => tag.substring(1)) // Remove @
1224
+ .filter(tag => tag.length > 0); // Filter out empty tags
1225
+
1226
+ return [...new Set(tags)]; // Remove duplicates
1227
+ }
1228
+
1229
+ mergeTags(explicitTags, titleTags) {
1230
+ const allTags = [];
1231
+
1232
+ // Add explicit tags (remove @ prefix if present)
1233
+ if (explicitTags && Array.isArray(explicitTags)) {
1234
+ allTags.push(...explicitTags.map(tag => tag.replace(/^@/, '')));
1235
+ }
1236
+
1237
+ // Add tags extracted from title
1238
+ if (titleTags && Array.isArray(titleTags)) {
1239
+ allTags.push(...titleTags);
1240
+ }
1241
+
1242
+ // Remove duplicates while preserving order
1243
+ const uniqueTags = [];
1244
+ const seen = new Set();
1245
+
1246
+ for (const tag of allTags) {
1247
+ if (!seen.has(tag) && tag.length > 0) {
1248
+ seen.add(tag);
1249
+ uniqueTags.push(tag);
1250
+ }
1251
+ }
1252
+
1253
+ return uniqueTags;
1254
+ }
1255
+
1133
1256
  async createTest(args) {
1134
1257
  const { suite_id, labels_ids, fields, ...attributes } = args;
1135
1258
 
1259
+ // Extract tags from title if provided
1260
+ const titleTags = this.extractTagsFromTitle(attributes.title);
1261
+ const mergedTags = this.mergeTags(attributes.tags, titleTags);
1262
+
1136
1263
  // Convert attributes to use hyphens instead of underscores for API compatibility
1137
1264
  const apiAttributes = Object.fromEntries(
1138
1265
  Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])
@@ -1149,6 +1276,7 @@ class TestomatioMCPServer {
1149
1276
  type: 'tests',
1150
1277
  attributes: {
1151
1278
  ...apiAttributes,
1279
+ ...(mergedTags.length > 0 && { tags: mergedTags }),
1152
1280
  ...(labels_ids && { labels_ids: labels_ids }),
1153
1281
  ...(fields && { 'custom-fields': fields })
1154
1282
  }
@@ -1198,9 +1326,60 @@ class TestomatioMCPServer {
1198
1326
  }
1199
1327
  }
1200
1328
 
1329
+ async unlinkLabel(args) {
1330
+ const { label_id, test_id, suite_id, value } = args;
1331
+
1332
+ // Validate that either test_id or suite_id is provided
1333
+ if (!test_id && !suite_id) {
1334
+ throw new Error('Either test_id or suite_id must be provided');
1335
+ }
1336
+
1337
+ if (test_id && suite_id) {
1338
+ throw new Error('Cannot specify both test_id and suite_id. Use one or the other.');
1339
+ }
1340
+
1341
+ // Build URL with base label link endpoint
1342
+ let url = `/labels/${label_id}/link`;
1343
+
1344
+ // Add appropriate query parameters
1345
+ if (test_id) {
1346
+ url += `?test_id=${test_id}`;
1347
+ } else if (suite_id) {
1348
+ url += `?suite_id=${suite_id}`;
1349
+ }
1350
+
1351
+ // Add event=remove parameter
1352
+ url += `&event=remove`;
1353
+
1354
+ // Add value as query parameter if present
1355
+ if (value) {
1356
+ url += `&value=${encodeURIComponent(value)}`;
1357
+ }
1358
+
1359
+ // Make the API request
1360
+ await this.makePostRequest(url, {});
1361
+
1362
+ const itemType = test_id ? 'test' : 'suite';
1363
+ const itemId = test_id || suite_id;
1364
+ const removeDescription = value ? `value "${value}"` : 'all instances';
1365
+
1366
+ return {
1367
+ content: [
1368
+ {
1369
+ type: 'text',
1370
+ text: `Successfully removed label "${label_id}" (${removeDescription}) from ${itemType} "${itemId}"`,
1371
+ },
1372
+ ],
1373
+ };
1374
+ }
1375
+
1201
1376
  async updateTest(args) {
1202
1377
  const { test_id, suite_id, labels_ids, fields, ...attributes } = args;
1203
1378
 
1379
+ // Extract tags from title if provided
1380
+ const titleTags = this.extractTagsFromTitle(attributes.title);
1381
+ const mergedTags = this.mergeTags(attributes.tags, titleTags);
1382
+
1204
1383
  // Convert attributes to use hyphens instead of underscores for API compatibility
1205
1384
  const apiAttributes = Object.fromEntries(
1206
1385
  Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])
@@ -1220,6 +1399,7 @@ class TestomatioMCPServer {
1220
1399
  type: 'tests',
1221
1400
  attributes: {
1222
1401
  ...apiAttributes,
1402
+ ...(mergedTags.length > 0 && { tags: mergedTags }),
1223
1403
  ...(fields && { 'custom-fields': fields })
1224
1404
  }
1225
1405
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "index.js",
6
6
  "bin": {