@testomatio/mcp 1.0.4 → 1.0.6

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 +73 -5
  2. package/index.js +82 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -112,19 +112,80 @@ 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`) — api: POST `/suites`
123
- * `create_folder` – Create a new folder (params: `title`, `description`, `parent_id`) — api: POST `/suites`
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
124
 
125
125
  #### Labels
126
126
  * `create_label` – Create a new label with optional custom field (params: `title`, `color`, `scope`, `visibility`, `field`) — api: POST `/labels`
127
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
+ ```
188
+
128
189
  #### Test Runs
129
190
  * `get_runs` – List all runs (no params) — api: GET `/runs`
130
191
  * `get_run` – Get one run (params: `run_id`, `tree`) — api: GET `/runs/{run_id}`
@@ -146,8 +207,11 @@ Once configured, you can ask your AI assistant questions like:
146
207
  - "Get all test plans for this project"
147
208
  - "Create a new test called 'Login validation' in suite suite-123"
148
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"
149
212
  - "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"
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"
151
215
  - "Create a label called 'Severity' with color '#ffe9ad' and predefined values like 'Blocker', 'Critical', 'Major', 'Minor', 'Normal', 'Trivial'"
152
216
 
153
217
  ## Query Patterns
@@ -168,6 +232,8 @@ These queries allow creating and updating tests:
168
232
  - **"Create a new test called 'Login validation' in suite suite-123"** → `create_test` tool with `title: "Login validation"`, `suite_id: "suite-123"`
169
233
  - **"Update test test-456 to change its description"** → `update_test` tool with `test_id: "test-456"`, `description: "new description"`
170
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" }`
171
237
 
172
238
  ### Suite and Folder Management Queries
173
239
 
@@ -175,7 +241,9 @@ These queries help organize your test structure:
175
241
 
176
242
  - **"Create a new suite called 'Authentication Tests'"** → `create_suite` tool with `title: "Authentication Tests"`
177
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" }`
178
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" }`
179
247
  - **"Create a test suite for payment features"** → `create_suite` tool with `title: "Payment Features", description: "Tests covering payment processing"`
180
248
  - **"Create a folder to organize integration tests"** → `create_folder` tool with `title: "Integration Tests"`
181
249
 
package/index.js CHANGED
@@ -8,9 +8,29 @@ import {
8
8
  } from '@modelcontextprotocol/sdk/types.js';
9
9
  import { program } from 'commander';
10
10
 
11
+ function normalizeString(value) {
12
+ return typeof value === 'string' ? value.trim() : value;
13
+ }
14
+
15
+ function normalizeBaseUrl(value) {
16
+ if (typeof value !== 'string') {
17
+ return value;
18
+ }
19
+
20
+ const trimmed = value.trim();
21
+ // Remove any internal whitespace characters that may appear when the URL
22
+ // gets broken across lines (e.g. "http://\n localhost:3000").
23
+ return trimmed.replace(/\s+/g, '');
24
+ }
25
+
11
26
  class TestomatioMCPServer {
12
27
  constructor(config) {
13
- this.config = config;
28
+ this.config = {
29
+ ...config,
30
+ token: normalizeString(config.token),
31
+ projectId: normalizeString(config.projectId),
32
+ baseUrl: normalizeBaseUrl(config.baseUrl),
33
+ };
14
34
  this.jwtToken = null;
15
35
  this.server = new Server(
16
36
  {
@@ -318,7 +338,14 @@ class TestomatioMCPServer {
318
338
  labels_ids: {
319
339
  type: 'array',
320
340
  items: { type: 'string' },
321
- description: 'Slugs of labels to assign to the test',
341
+ description: 'Slugs of labels to assign to the test. Supports label:value format (e.g., ["priority:high", "severity:critical"])',
342
+ },
343
+ fields: {
344
+ type: 'object',
345
+ description: 'Set custom fields for the test. Object with field names as keys and values as properties (e.g., {"priority": "high", "severity": "critical"})',
346
+ additionalProperties: {
347
+ type: 'string'
348
+ },
322
349
  },
323
350
  },
324
351
  required: ['suite_id', 'title'],
@@ -381,7 +408,14 @@ class TestomatioMCPServer {
381
408
  labels_ids: {
382
409
  type: 'array',
383
410
  items: { type: 'string' },
384
- description: 'Slugs of labels to assign to the test',
411
+ description: 'Slugs of labels to assign to the test. Supports label:value format (e.g., ["priority:high", "severity:critical"])',
412
+ },
413
+ fields: {
414
+ type: 'object',
415
+ description: 'Set custom fields for the test. Object with field names as keys and values as properties (e.g., {"priority": "high", "severity": "critical"})',
416
+ additionalProperties: {
417
+ type: 'string'
418
+ },
385
419
  },
386
420
  },
387
421
  required: ['test_id'],
@@ -405,6 +439,13 @@ class TestomatioMCPServer {
405
439
  type: 'string',
406
440
  description: 'Parent suite ID to create this suite under',
407
441
  },
442
+ fields: {
443
+ type: 'object',
444
+ description: 'Set custom fields for the suite. Object with field names as keys and values as properties (e.g., {"priority": "high", "team": "backend"})',
445
+ additionalProperties: {
446
+ type: 'string'
447
+ },
448
+ },
408
449
  },
409
450
  required: ['title'],
410
451
  },
@@ -427,6 +468,13 @@ class TestomatioMCPServer {
427
468
  type: 'string',
428
469
  description: 'Parent folder or suite ID to create this folder under',
429
470
  },
471
+ fields: {
472
+ type: 'object',
473
+ description: 'Set custom fields for the folder. Object with field names as keys and values as properties (e.g., {"priority": "high", "team": "backend"})',
474
+ additionalProperties: {
475
+ type: 'string'
476
+ },
477
+ },
430
478
  },
431
479
  required: ['title'],
432
480
  },
@@ -1022,14 +1070,19 @@ class TestomatioMCPServer {
1022
1070
  }
1023
1071
 
1024
1072
  async createTest(args) {
1025
- const { suite_id, labels_ids, ...attributes } = args;
1073
+ const { suite_id, labels_ids, fields, ...attributes } = args;
1074
+
1075
+ // Handle fields parameter for custom fields
1026
1076
  const requestData = {
1027
1077
  data: {
1028
1078
  type: 'tests',
1029
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
1079
+ attributes: {
1080
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1081
+ ...(fields && { 'custom-fields': fields })
1082
+ }
1030
1083
  }
1031
1084
  };
1032
-
1085
+
1033
1086
  if (suite_id) {
1034
1087
  requestData.data.relationships = {
1035
1088
  suite: {
@@ -1040,7 +1093,7 @@ class TestomatioMCPServer {
1040
1093
  }
1041
1094
  };
1042
1095
  }
1043
-
1096
+
1044
1097
  if (labels_ids) requestData.labels_ids = labels_ids;
1045
1098
 
1046
1099
  const data = await this.makePostRequest('/tests', requestData);
@@ -1060,14 +1113,19 @@ class TestomatioMCPServer {
1060
1113
  }
1061
1114
 
1062
1115
  async updateTest(args) {
1063
- const { test_id, suite_id, labels_ids, ...attributes } = args;
1116
+ const { test_id, suite_id, labels_ids, fields, ...attributes } = args;
1117
+
1118
+ // Handle fields parameter for custom fields
1064
1119
  const requestData = {
1065
1120
  data: {
1066
1121
  type: 'tests',
1067
- attributes: Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v]))
1122
+ attributes: {
1123
+ ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1124
+ ...(fields && { 'custom-fields': fields })
1125
+ }
1068
1126
  }
1069
1127
  };
1070
-
1128
+
1071
1129
  if (suite_id) {
1072
1130
  requestData.data.relationships = {
1073
1131
  suite: {
@@ -1078,7 +1136,7 @@ class TestomatioMCPServer {
1078
1136
  }
1079
1137
  };
1080
1138
  }
1081
-
1139
+
1082
1140
  if (labels_ids) requestData.labels_ids = labels_ids;
1083
1141
 
1084
1142
  const data = await this.makePutRequest(`/tests/${test_id}`, requestData);
@@ -1098,13 +1156,14 @@ class TestomatioMCPServer {
1098
1156
  }
1099
1157
 
1100
1158
  async createSuite(args) {
1101
- const { parent_id, ...attributes } = args;
1159
+ const { parent_id, fields, ...attributes } = args;
1102
1160
  const requestData = {
1103
1161
  data: {
1104
1162
  type: 'suites',
1105
1163
  attributes: {
1106
1164
  ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1107
- 'file-type': 'file'
1165
+ 'file-type': 'file',
1166
+ ...(fields && { 'custom-fields': fields })
1108
1167
  }
1109
1168
  }
1110
1169
  };
@@ -1136,13 +1195,14 @@ class TestomatioMCPServer {
1136
1195
  }
1137
1196
 
1138
1197
  async createFolder(args) {
1139
- const { parent_id, ...attributes } = args;
1198
+ const { parent_id, fields, ...attributes } = args;
1140
1199
  const requestData = {
1141
1200
  data: {
1142
1201
  type: 'suites',
1143
1202
  attributes: {
1144
1203
  ...Object.fromEntries(Object.entries(attributes).map(([k, v]) => [k.replace(/_/g, '-'), v])),
1145
- 'file-type': 'folder'
1204
+ 'file-type': 'folder',
1205
+ ...(fields && { 'custom-fields': fields })
1146
1206
  }
1147
1207
  }
1148
1208
  };
@@ -1245,9 +1305,13 @@ function parseArgs() {
1245
1305
 
1246
1306
  const options = program.opts();
1247
1307
 
1248
- const token = options.token || process.env.TESTOMATIO_API_TOKEN;
1249
- const projectId = options.project || process.env.TESTOMATIO_PROJECT_ID;
1250
- const baseUrl = options.baseUrl || process.env.TESTOMATIO_BASE_URL || 'https://app.testomat.io';
1308
+ const token = normalizeString(options.token || process.env.TESTOMATIO_API_TOKEN);
1309
+ const projectId = normalizeString(options.project || process.env.TESTOMATIO_PROJECT_ID);
1310
+ const baseUrl = normalizeBaseUrl(
1311
+ options.baseUrl ||
1312
+ process.env.TESTOMATIO_BASE_URL ||
1313
+ 'https://app.testomat.io'
1314
+ );
1251
1315
 
1252
1316
  if (!token) {
1253
1317
  console.error('Error: API token is required. Use --token <token> or set TESTOMATIO_API_TOKEN environment variable');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "index.js",
6
6
  "bin": {