@testomatio/mcp 2.0.0-beta.9 → 2.0.1-beta

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 CHANGED
@@ -8,17 +8,19 @@ Model Context Protocol (MCP) server that enables AI assistants (Claude, Cursor,
8
8
  - Tests, Suites, Plans, Runs, TestRuns, RunGroups, Steps, Snippets, Labels
9
9
  - Tags (read-only access)
10
10
  - Issues (global + scoped helpers for tests/suites/runs/testruns/plans)
11
- - **Smart Search** - delegates to list endpoints with query/filter forwarding
11
+ - **Smart Search** - delegates to list endpoints with OpenAPI-aligned query/filter forwarding
12
12
  - **Issue Linking** - link/unlink issues to any resource
13
13
  - **API Compatibility** - automatic handling of payload format differences (flat vs wrapped)
14
14
  - **Run Management** - status transitions via `status_event` parameter
15
+ - **TQL-Only Search** - `tests_list/tests_search` and `runs_list/runs_search` use `tql` as the single search/filter input
16
+ - **Safe TQL Guidance** - prefer documented expressions like `priority == high`, `state == automated`, `size == 5`, `size > 1`; do not guess undocumented TQL syntax; fall back only when the API rejects the TQL expression or the needed field is not supported
15
17
 
16
18
  ## Quick Start
17
19
 
18
20
  ### Installation
19
21
 
20
22
  ```bash
21
- npm install -g @testomatio/mcp
23
+ npm install -g @testomatio/mcp@latest
22
24
  ```
23
25
 
24
26
  ### Configuration
@@ -48,17 +50,24 @@ export TESTOMATIO_BASE_URL=https://beta.testomat.io
48
50
 
49
51
  ### Cursor IDE
50
52
 
51
- Add to `.cursorrules` or settings.json:
53
+ Add this config to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for global access:
52
54
 
53
55
  ```json
54
56
  {
55
57
  "mcpServers": {
56
58
  "testomatio": {
57
- "command": "testomatio-mcp",
58
- "args": ["--token", "<TOKEN>", "--project", "<PROJECT_ID>"],
59
+ "type": "stdio",
60
+ "command": "npx",
61
+ "args": [
62
+ "-y",
63
+ "@testomatio/mcp@latest",
64
+ "--token",
65
+ "<TOKEN>",
66
+ "--project",
67
+ "<PROJECT_ID>"
68
+ ],
59
69
  "env": {
60
- "TESTOMATIO_PROJECT_TOKEN": "<TOKEN>",
61
- "TESTOMATIO_PROJECT_ID": "<PROJECT_ID>"
70
+ "TESTOMATIO_BASE_URL": "https://app.testomat.io"
62
71
  }
63
72
  }
64
73
  }
@@ -67,12 +76,26 @@ Add to `.cursorrules` or settings.json:
67
76
 
68
77
  ### Claude Desktop
69
78
 
79
+ Add this config to:
80
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
81
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
82
+
70
83
  ```json
71
84
  {
72
85
  "mcpServers": {
73
86
  "testomatio": {
74
- "command": "node",
75
- "args": ["/path/to/mcp/index.js", "--token", "<TOKEN>", "--project", "<PROJECT_ID>"]
87
+ "command": "npx",
88
+ "args": [
89
+ "-y",
90
+ "@testomatio/mcp@latest",
91
+ "--token",
92
+ "<TOKEN>",
93
+ "--project",
94
+ "<PROJECT_ID>"
95
+ ],
96
+ "env": {
97
+ "TESTOMATIO_BASE_URL": "https://app.testomat.io"
98
+ }
76
99
  }
77
100
  }
78
101
  }
@@ -80,7 +103,7 @@ Add to `.cursorrules` or settings.json:
80
103
 
81
104
  ### OpenCode
82
105
 
83
- Create `.opencode/opencode.json`:
106
+ Add this config to `opencode.json` in your project root, or to `~/.config/opencode/opencode.json` for global access:
84
107
 
85
108
  ```json
86
109
  {
@@ -89,8 +112,9 @@ Create `.opencode/opencode.json`:
89
112
  "testomat": {
90
113
  "type": "local",
91
114
  "command": [
92
- "node",
93
- "node_modules/@testomatio/mcp/index.js",
115
+ "npx",
116
+ "-y",
117
+ "@testomatio/mcp@latest",
94
118
  "--token",
95
119
  "<TOKEN>",
96
120
  "--project",
@@ -111,7 +135,7 @@ Create `.opencode/opencode.json`:
111
135
  ```json
112
136
  {
113
137
  "name": "tests_list",
114
- "arguments": { "page": 1, "per_page": 50 }
138
+ "arguments": { "page": 1, "per_page": 50, "tql": "priority == high" }
115
139
  }
116
140
  ```
117
141
 
@@ -156,16 +180,16 @@ Complete tool reference: [docs/tools.md](./docs/tools.md)
156
180
 
157
181
  ## Project Structure
158
182
 
159
- ```
183
+ ```text
160
184
  src/
161
- ├── config/ # Config loading, defaults
162
- ├── core/ # Errors, logger
163
- ├── api/ # HTTP client, Testomat.io API client
164
- ├── mcp/ # MCP server, tools, registry
165
- │ ├── definitions/ # Tool definitions by entity
166
- │ ├── configs/ # Registry generation configs
167
- │ └── registry/ # Tool handlers
168
- └── cli/ # CLI bootstrap
185
+ |- config/ # Config loading, defaults
186
+ |- core/ # Errors, logger
187
+ |- api/ # HTTP client, Testomat.io API client
188
+ |- mcp/ # MCP server, tools, registry
189
+ | |- definitions/ # Tool definitions by entity
190
+ | |- configs/ # Registry generation configs
191
+ | `- registry/ # Tool handlers
192
+ `- cli/ # CLI bootstrap
169
193
  ```
170
194
 
171
195
  ## Environment Variables
@@ -179,10 +203,45 @@ src/
179
203
 
180
204
  *Either `TESTOMATIO_PROJECT_TOKEN` or `TESTOMATIO_API_TOKEN`
181
205
 
206
+ ## Corporate TLS Certificates
207
+
208
+ If the MCP server runs behind a corporate proxy or TLS inspection, Node.js may reject Testomat.io HTTPS requests even when the same URL works in a browser. This usually means the company root certificate is trusted by the operating system, but not by Node.js.
209
+
210
+ Use Node.js system CA support:
211
+
212
+ ```bash
213
+ NODE_OPTIONS=--use-system-ca testomatio-mcp --token <TOKEN> --project <PROJECT_ID>
214
+ ```
215
+
216
+ For MCP clients, pass `NODE_OPTIONS` in the server environment:
217
+
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "testomatio": {
222
+ "command": "testomatio-mcp",
223
+ "args": ["--token", "<TOKEN>", "--project", "<PROJECT_ID>"],
224
+ "env": {
225
+ "NODE_OPTIONS": "--use-system-ca"
226
+ }
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ If your Node.js version does not support `--use-system-ca`, export the corporate root certificate to a PEM file and use Node.js extra CA support:
233
+
234
+ ```bash
235
+ NODE_EXTRA_CA_CERTS=/path/to/company-root-ca.pem testomatio-mcp --token <TOKEN> --project <PROJECT_ID>
236
+ ```
237
+
182
238
  ## Important Notes
183
239
 
184
240
  - **Run Status** - Use `runs_update` with `status_event` for transitions (finish, launch, rerun, etc.)
185
- - **Search** - No dedicated `/search` endpoints; search uses list with filters
241
+ - **Search** - No dedicated `/search` endpoints. MCP search tools delegate to list tools; for `tests` and `runs` the MCP interface is intentionally simplified to `tql`, while other entities stay closer to Public API v2 filters
242
+ - **TQL** - Use `tql` as the single search/filter input for `tests_list/tests_search` and `runs_list/runs_search`
243
+ - **TQL Safety** - Prefer documented expressions like `priority == high`, `state == automated`, `size == 5`, `size > 1`; do not invent tag-style or free-text syntax unless it was verified
244
+ - **Fallback Rule** - For `tests` and `runs`, try `tql` first. Use other tools or extra analysis only after the API rejects the TQL expression or the needed field is not supported by TQL
186
245
  - **Issue Linking** - Scoped helpers available: `{entity}_issues_link/unlink`
187
246
 
188
247
  ## Development
@@ -191,3 +250,16 @@ src/
191
250
  npm install
192
251
  npm run start -- --token <TOKEN> --project <PROJECT_ID>
193
252
  ```
253
+
254
+ For local MCP development, point Claude Desktop to the checked-out entrypoint:
255
+
256
+ ```json
257
+ {
258
+ "mcpServers": {
259
+ "testomatio-local": {
260
+ "command": "node",
261
+ "args": ["/path/to/mcp/index.js", "--token", "<TOKEN>", "--project", "<PROJECT_ID>"]
262
+ }
263
+ }
264
+ }
265
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "2.0.0-beta.9",
3
+ "version": "2.0.1-beta",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -46,7 +46,6 @@ export const ENTITY_CRUD_CONFIGS = [
46
46
  resource: 'rungroups',
47
47
  idArg: 'rungroup_id',
48
48
  listMethod: 'listRungroups',
49
- searchMethod: 'searchRungroups',
50
49
  payloadBuilder: 'buildRungroupPayload',
51
50
  wrapperKey: 'rungroup',
52
51
  createMode: 'wrapped',
@@ -57,7 +56,6 @@ export const ENTITY_CRUD_CONFIGS = [
57
56
  resource: 'steps',
58
57
  idArg: 'step_id',
59
58
  listMethod: 'listSteps',
60
- searchMethod: 'searchSteps',
61
59
  payloadBuilder: 'buildStepPayload',
62
60
  wrapperKey: 'step',
63
61
  createMode: 'wrapped',
@@ -68,7 +66,6 @@ export const ENTITY_CRUD_CONFIGS = [
68
66
  resource: 'snippets',
69
67
  idArg: 'snippet_id',
70
68
  listMethod: 'listSnippets',
71
- searchMethod: 'searchSnippets',
72
69
  payloadBuilder: 'buildSnippetPayload',
73
70
  wrapperKey: 'snippet',
74
71
  createMode: 'wrapped',
@@ -79,7 +76,6 @@ export const ENTITY_CRUD_CONFIGS = [
79
76
  resource: 'labels',
80
77
  idArg: 'label_id',
81
78
  listMethod: 'listLabels',
82
- searchMethod: 'searchLabels',
83
79
  payloadBuilder: 'buildLabelPayload',
84
80
  wrapperKey: 'label',
85
81
  createMode: 'wrapped',
@@ -13,9 +13,6 @@ export const LABELS_TOOLS = [
13
13
  "type": "integer",
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
- },
17
- "query": {
18
- "type": "string"
19
16
  }
20
17
  },
21
18
  "additionalProperties": false
@@ -148,26 +145,4 @@ export const LABELS_TOOLS = [
148
145
  "additionalProperties": false
149
146
  }
150
147
  },
151
- {
152
- "name": "labels_search",
153
- "description": "Search labels (delegates to labels list; docs has no dedicated search parameter)",
154
- "inputSchema": {
155
- "type": "object",
156
- "properties": {
157
- "query": {
158
- "type": "string"
159
- },
160
- "page": {
161
- "type": "integer",
162
- "minimum": 1
163
- },
164
- "per_page": {
165
- "type": "integer",
166
- "minimum": 1,
167
- "maximum": 100
168
- }
169
- },
170
- "additionalProperties": false
171
- }
172
- }
173
148
  ];
@@ -14,7 +14,24 @@ export const PLANS_TOOLS = [
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
16
  },
17
- "query": {
17
+ "kind": {
18
+ "type": "string",
19
+ "enum": [
20
+ "manual",
21
+ "automated",
22
+ "mixed"
23
+ ]
24
+ },
25
+ "hidden": {
26
+ "type": "boolean"
27
+ },
28
+ "labels": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "string"
32
+ }
33
+ },
34
+ "search_text": {
18
35
  "type": "string"
19
36
  }
20
37
  },
@@ -95,6 +112,7 @@ export const PLANS_TOOLS = [
95
112
  "label",
96
113
  "custom_field",
97
114
  "tag",
115
+ "milestone",
98
116
  "issue",
99
117
  "jira"
100
118
  ]
@@ -179,6 +197,7 @@ export const PLANS_TOOLS = [
179
197
  "label",
180
198
  "custom_field",
181
199
  "tag",
200
+ "milestone",
182
201
  "issue",
183
202
  "jira"
184
203
  ]
@@ -224,7 +243,7 @@ export const PLANS_TOOLS = [
224
243
  "inputSchema": {
225
244
  "type": "object",
226
245
  "properties": {
227
- "query": {
246
+ "search_text": {
228
247
  "type": "string"
229
248
  },
230
249
  "page": {
@@ -235,6 +254,23 @@ export const PLANS_TOOLS = [
235
254
  "type": "integer",
236
255
  "minimum": 1,
237
256
  "maximum": 100
257
+ },
258
+ "kind": {
259
+ "type": "string",
260
+ "enum": [
261
+ "manual",
262
+ "automated",
263
+ "mixed"
264
+ ]
265
+ },
266
+ "hidden": {
267
+ "type": "boolean"
268
+ },
269
+ "labels": {
270
+ "type": "array",
271
+ "items": {
272
+ "type": "string"
273
+ }
238
274
  }
239
275
  },
240
276
  "additionalProperties": false
@@ -13,9 +13,6 @@ export const RUNGROUPS_TOOLS = [
13
13
  "type": "integer",
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
- },
17
- "query": {
18
- "type": "string"
19
16
  }
20
17
  },
21
18
  "additionalProperties": false
@@ -130,26 +127,4 @@ export const RUNGROUPS_TOOLS = [
130
127
  "additionalProperties": false
131
128
  }
132
129
  },
133
- {
134
- "name": "rungroups_search",
135
- "description": "Search run groups (delegates to rungroups list; docs has no dedicated search parameter)",
136
- "inputSchema": {
137
- "type": "object",
138
- "properties": {
139
- "query": {
140
- "type": "string"
141
- },
142
- "page": {
143
- "type": "integer",
144
- "minimum": 1
145
- },
146
- "per_page": {
147
- "type": "integer",
148
- "minimum": 1,
149
- "maximum": 100
150
- }
151
- },
152
- "additionalProperties": false
153
- }
154
- }
155
130
  ];
@@ -1,7 +1,7 @@
1
1
  export const RUNS_TOOLS = [
2
2
  {
3
3
  "name": "runs_list",
4
- "description": "List runs (/api/v2/{project_id}/runs)",
4
+ "description": "List runs (/api/v2/{project_id}/runs). Use `tql` first for search/filtering. Prefer known-safe expressions such as `size == 5` or `size > 1`. Do not guess undocumented TQL syntax. Fall back to other tools or analysis only if the API rejects the TQL expression or the needed field is not supported by TQL.",
5
5
  "inputSchema": {
6
6
  "type": "object",
7
7
  "properties": {
@@ -14,8 +14,9 @@ export const RUNS_TOOLS = [
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
16
  },
17
- "query": {
18
- "type": "string"
17
+ "tql": {
18
+ "type": "string",
19
+ "description": "Universal TQL filter for runs. Prefer known-safe expressions like `size == 5` or `size > 1`. Do not guess undocumented syntax. Fall back only if the API rejects the TQL expression or the needed field is not supported by TQL."
19
20
  }
20
21
  },
21
22
  "additionalProperties": false
@@ -49,8 +50,11 @@ export const RUNS_TOOLS = [
49
50
  "description": {
50
51
  "type": "string"
51
52
  },
52
- "plan_id": {
53
- "type": "string"
53
+ "plan_ids": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "string"
57
+ }
54
58
  },
55
59
  "kind": {
56
60
  "type": "string",
@@ -95,9 +99,6 @@ export const RUNS_TOOLS = [
95
99
  "type": "string"
96
100
  }
97
101
  },
98
- "configuration": {
99
- "type": "object"
100
- },
101
102
  "link": {
102
103
  "type": "array",
103
104
  "items": {
@@ -116,6 +117,7 @@ export const RUNS_TOOLS = [
116
117
  "label",
117
118
  "custom_field",
118
119
  "tag",
120
+ "milestone",
119
121
  "issue",
120
122
  "jira"
121
123
  ]
@@ -154,9 +156,6 @@ export const RUNS_TOOLS = [
154
156
  "description": {
155
157
  "type": "string"
156
158
  },
157
- "plan_id": {
158
- "type": "string"
159
- },
160
159
  "kind": {
161
160
  "type": "string",
162
161
  "enum": [
@@ -205,9 +204,6 @@ export const RUNS_TOOLS = [
205
204
  "type": "string"
206
205
  }
207
206
  },
208
- "configuration": {
209
- "type": "object"
210
- },
211
207
  "link": {
212
208
  "type": "array",
213
209
  "items": {
@@ -226,6 +222,7 @@ export const RUNS_TOOLS = [
226
222
  "label",
227
223
  "custom_field",
228
224
  "tag",
225
+ "milestone",
229
226
  "issue",
230
227
  "jira"
231
228
  ]
@@ -267,13 +264,10 @@ export const RUNS_TOOLS = [
267
264
  },
268
265
  {
269
266
  "name": "runs_search",
270
- "description": "Search runs (delegates to runs list; docs has no dedicated search parameter)",
267
+ "description": "Search runs using TQL (delegates to runs_list). Use `tql` first. Prefer known-safe expressions such as `size == 5` or `size > 1`. Do not guess undocumented TQL syntax. Fall back to other tools or analysis only if the API rejects the TQL expression or the needed field is not supported by TQL.",
271
268
  "inputSchema": {
272
269
  "type": "object",
273
270
  "properties": {
274
- "query": {
275
- "type": "string"
276
- },
277
271
  "page": {
278
272
  "type": "integer",
279
273
  "minimum": 1
@@ -282,6 +276,10 @@ export const RUNS_TOOLS = [
282
276
  "type": "integer",
283
277
  "minimum": 1,
284
278
  "maximum": 100
279
+ },
280
+ "tql": {
281
+ "type": "string",
282
+ "description": "Universal TQL filter for runs. Prefer known-safe expressions like `size == 5` or `size > 1`. Do not guess undocumented syntax. Fall back only if the API rejects the TQL expression or the needed field is not supported by TQL."
285
283
  }
286
284
  },
287
285
  "additionalProperties": false
@@ -13,9 +13,6 @@ export const SNIPPETS_TOOLS = [
13
13
  "type": "integer",
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
- },
17
- "query": {
18
- "type": "string"
19
16
  }
20
17
  },
21
18
  "additionalProperties": false
@@ -67,6 +64,7 @@ export const SNIPPETS_TOOLS = [
67
64
  "label",
68
65
  "custom_field",
69
66
  "tag",
67
+ "milestone",
70
68
  "issue",
71
69
  "jira"
72
70
  ]
@@ -123,6 +121,7 @@ export const SNIPPETS_TOOLS = [
123
121
  "label",
124
122
  "custom_field",
125
123
  "tag",
124
+ "milestone",
126
125
  "issue",
127
126
  "jira"
128
127
  ]
@@ -162,26 +161,4 @@ export const SNIPPETS_TOOLS = [
162
161
  "additionalProperties": false
163
162
  }
164
163
  },
165
- {
166
- "name": "snippets_search",
167
- "description": "Search snippets (delegates to snippets list; docs has no dedicated search parameter)",
168
- "inputSchema": {
169
- "type": "object",
170
- "properties": {
171
- "query": {
172
- "type": "string"
173
- },
174
- "page": {
175
- "type": "integer",
176
- "minimum": 1
177
- },
178
- "per_page": {
179
- "type": "integer",
180
- "minimum": 1,
181
- "maximum": 100
182
- }
183
- },
184
- "additionalProperties": false
185
- }
186
- }
187
164
  ];
@@ -13,9 +13,6 @@ export const STEPS_TOOLS = [
13
13
  "type": "integer",
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
- },
17
- "query": {
18
- "type": "string"
19
16
  }
20
17
  },
21
18
  "additionalProperties": false
@@ -67,6 +64,7 @@ export const STEPS_TOOLS = [
67
64
  "label",
68
65
  "custom_field",
69
66
  "tag",
67
+ "milestone",
70
68
  "issue",
71
69
  "jira"
72
70
  ]
@@ -123,6 +121,7 @@ export const STEPS_TOOLS = [
123
121
  "label",
124
122
  "custom_field",
125
123
  "tag",
124
+ "milestone",
126
125
  "issue",
127
126
  "jira"
128
127
  ]
@@ -162,26 +161,4 @@ export const STEPS_TOOLS = [
162
161
  "additionalProperties": false
163
162
  }
164
163
  },
165
- {
166
- "name": "steps_search",
167
- "description": "Search steps (delegates to steps list; docs has no dedicated search parameter)",
168
- "inputSchema": {
169
- "type": "object",
170
- "properties": {
171
- "query": {
172
- "type": "string"
173
- },
174
- "page": {
175
- "type": "integer",
176
- "minimum": 1
177
- },
178
- "per_page": {
179
- "type": "integer",
180
- "minimum": 1,
181
- "maximum": 100
182
- }
183
- },
184
- "additionalProperties": false
185
- }
186
- }
187
164
  ];
@@ -29,9 +29,6 @@ export const SUITES_TOOLS = [
29
29
  },
30
30
  "search_text": {
31
31
  "type": "string"
32
- },
33
- "query": {
34
- "type": "string"
35
32
  }
36
33
  },
37
34
  "additionalProperties": false
@@ -101,8 +98,10 @@ export const SUITES_TOOLS = [
101
98
  "label",
102
99
  "custom_field",
103
100
  "tag",
101
+ "milestone",
104
102
  "issue",
105
- "jira"
103
+ "jira",
104
+ "requirement"
106
105
  ]
107
106
  },
108
107
  "value": {
@@ -175,8 +174,10 @@ export const SUITES_TOOLS = [
175
174
  "label",
176
175
  "custom_field",
177
176
  "tag",
177
+ "milestone",
178
178
  "issue",
179
- "jira"
179
+ "jira",
180
+ "requirement"
180
181
  ]
181
182
  },
182
183
  "value": {
@@ -223,9 +224,6 @@ export const SUITES_TOOLS = [
223
224
  "search_text": {
224
225
  "type": "string"
225
226
  },
226
- "query": {
227
- "type": "string"
228
- },
229
227
  "page": {
230
228
  "type": "integer",
231
229
  "minimum": 1
@@ -17,8 +17,104 @@ export const TESTRUNS_TOOLS = [
17
17
  "run_id": {
18
18
  "type": "string"
19
19
  },
20
- "query": {
20
+ "test_ids": {
21
+ "type": [
22
+ "array",
23
+ "string"
24
+ ],
25
+ "items": {
26
+ "type": "string"
27
+ }
28
+ },
29
+ "filter_status": {
30
+ "type": "string",
31
+ "enum": [
32
+ "passed",
33
+ "failed",
34
+ "skipped",
35
+ "pending"
36
+ ]
37
+ },
38
+ "filter_kind": {
39
+ "type": "string",
40
+ "enum": [
41
+ "manual",
42
+ "automated"
43
+ ]
44
+ },
45
+ "filter_user": {
46
+ "type": [
47
+ "integer",
48
+ "string"
49
+ ]
50
+ },
51
+ "filter_priority": {
52
+ "type": "string",
53
+ "enum": [
54
+ "low",
55
+ "normal",
56
+ "important",
57
+ "high",
58
+ "critical"
59
+ ]
60
+ },
61
+ "filter_substatus": {
21
62
  "type": "string"
63
+ },
64
+ "filter_search": {
65
+ "type": "string"
66
+ },
67
+ "filter_message": {
68
+ "type": "boolean"
69
+ },
70
+ "filter_link": {
71
+ "type": "boolean"
72
+ },
73
+ "filter_finished_at_date_range": {
74
+ "type": "string"
75
+ },
76
+ "tags": {
77
+ "type": [
78
+ "array",
79
+ "string"
80
+ ],
81
+ "items": {
82
+ "type": "string"
83
+ }
84
+ },
85
+ "labels": {
86
+ "type": [
87
+ "array",
88
+ "string"
89
+ ],
90
+ "items": {
91
+ "type": "string"
92
+ }
93
+ },
94
+ "envs": {
95
+ "type": [
96
+ "array",
97
+ "string"
98
+ ],
99
+ "items": {
100
+ "type": "string"
101
+ }
102
+ },
103
+ "rungroups": {
104
+ "type": [
105
+ "array",
106
+ "string"
107
+ ],
108
+ "items": {
109
+ "type": "string"
110
+ }
111
+ },
112
+ "defects": {
113
+ "type": "string",
114
+ "enum": [
115
+ "has_defects",
116
+ "without_defects"
117
+ ]
22
118
  }
23
119
  },
24
120
  "additionalProperties": false
@@ -142,7 +238,51 @@ export const TESTRUNS_TOOLS = [
142
238
  "run_id": {
143
239
  "type": "string"
144
240
  },
145
- "query": {
241
+ "test_ids": {
242
+ "type": [
243
+ "array",
244
+ "string"
245
+ ],
246
+ "items": {
247
+ "type": "string"
248
+ }
249
+ },
250
+ "filter_status": {
251
+ "type": "string",
252
+ "enum": [
253
+ "passed",
254
+ "failed",
255
+ "skipped",
256
+ "pending"
257
+ ]
258
+ },
259
+ "filter_kind": {
260
+ "type": "string",
261
+ "enum": [
262
+ "manual",
263
+ "automated"
264
+ ]
265
+ },
266
+ "filter_user": {
267
+ "type": [
268
+ "integer",
269
+ "string"
270
+ ]
271
+ },
272
+ "filter_priority": {
273
+ "type": "string",
274
+ "enum": [
275
+ "low",
276
+ "normal",
277
+ "important",
278
+ "high",
279
+ "critical"
280
+ ]
281
+ },
282
+ "filter_substatus": {
283
+ "type": "string"
284
+ },
285
+ "filter_search": {
146
286
  "type": "string"
147
287
  },
148
288
  "page": {
@@ -153,6 +293,58 @@ export const TESTRUNS_TOOLS = [
153
293
  "type": "integer",
154
294
  "minimum": 1,
155
295
  "maximum": 100
296
+ },
297
+ "filter_message": {
298
+ "type": "boolean"
299
+ },
300
+ "filter_link": {
301
+ "type": "boolean"
302
+ },
303
+ "filter_finished_at_date_range": {
304
+ "type": "string"
305
+ },
306
+ "tags": {
307
+ "type": [
308
+ "array",
309
+ "string"
310
+ ],
311
+ "items": {
312
+ "type": "string"
313
+ }
314
+ },
315
+ "labels": {
316
+ "type": [
317
+ "array",
318
+ "string"
319
+ ],
320
+ "items": {
321
+ "type": "string"
322
+ }
323
+ },
324
+ "envs": {
325
+ "type": [
326
+ "array",
327
+ "string"
328
+ ],
329
+ "items": {
330
+ "type": "string"
331
+ }
332
+ },
333
+ "rungroups": {
334
+ "type": [
335
+ "array",
336
+ "string"
337
+ ],
338
+ "items": {
339
+ "type": "string"
340
+ }
341
+ },
342
+ "defects": {
343
+ "type": "string",
344
+ "enum": [
345
+ "has_defects",
346
+ "without_defects"
347
+ ]
156
348
  }
157
349
  },
158
350
  "additionalProperties": false
@@ -1,7 +1,7 @@
1
1
  export const TESTS_TOOLS = [
2
2
  {
3
3
  "name": "tests_list",
4
- "description": "List tests (/api/v2/{project_id}/tests)",
4
+ "description": "List tests (/api/v2/{project_id}/tests). Use `tql` first for search/filtering. Prefer known-safe expressions such as `priority == high` or `state == automated`. Do not guess undocumented TQL syntax. Fall back to other tools or analysis only if the API rejects the TQL expression or the needed field is not supported by TQL.",
5
5
  "inputSchema": {
6
6
  "type": "object",
7
7
  "properties": {
@@ -14,23 +14,9 @@ export const TESTS_TOOLS = [
14
14
  "minimum": 1,
15
15
  "maximum": 100
16
16
  },
17
- "suite_id": {
18
- "type": "string"
19
- },
20
- "search_text": {
21
- "type": "string"
22
- },
23
- "query": {
24
- "type": "string"
25
- },
26
- "assigned_to": {
27
- "type": "string"
28
- },
29
- "priority": {
30
- "type": "string"
31
- },
32
- "state": {
33
- "type": "string"
17
+ "tql": {
18
+ "type": "string",
19
+ "description": "Universal TQL filter for tests. Prefer known-safe expressions like `priority == high` or `state == automated`. Do not guess undocumented syntax. Fall back only if the API rejects the TQL expression or the needed field is not supported by TQL."
34
20
  }
35
21
  },
36
22
  "additionalProperties": false
@@ -100,6 +86,7 @@ export const TESTS_TOOLS = [
100
86
  "label",
101
87
  "custom_field",
102
88
  "tag",
89
+ "milestone",
103
90
  "issue",
104
91
  "jira"
105
92
  ]
@@ -178,6 +165,7 @@ export const TESTS_TOOLS = [
178
165
  "label",
179
166
  "custom_field",
180
167
  "tag",
168
+ "milestone",
181
169
  "issue",
182
170
  "jira"
183
171
  ]
@@ -219,16 +207,10 @@ export const TESTS_TOOLS = [
219
207
  },
220
208
  {
221
209
  "name": "tests_search",
222
- "description": "Search tests by text (delegates to tests list with search_text)",
210
+ "description": "Search tests using TQL (delegates to tests_list). Use `tql` first. Prefer known-safe expressions such as `priority == high` or `state == automated`. Do not guess undocumented TQL syntax. Fall back to other tools or analysis only if the API rejects the TQL expression or the needed field is not supported by TQL.",
223
211
  "inputSchema": {
224
212
  "type": "object",
225
213
  "properties": {
226
- "query": {
227
- "type": "string"
228
- },
229
- "search_text": {
230
- "type": "string"
231
- },
232
214
  "page": {
233
215
  "type": "integer",
234
216
  "minimum": 1
@@ -238,17 +220,9 @@ export const TESTS_TOOLS = [
238
220
  "minimum": 1,
239
221
  "maximum": 100
240
222
  },
241
- "suite_id": {
242
- "type": "string"
243
- },
244
- "assigned_to": {
245
- "type": "string"
246
- },
247
- "priority": {
248
- "type": "string"
249
- },
250
- "state": {
251
- "type": "string"
223
+ "tql": {
224
+ "type": "string",
225
+ "description": "Universal TQL filter for tests. Prefer known-safe expressions like `priority == high` or `state == automated`. Do not guess undocumented syntax. Fall back only if the API rejects the TQL expression or the needed field is not supported by TQL."
252
226
  }
253
227
  },
254
228
  "additionalProperties": false
@@ -8,8 +8,10 @@ export const handlerMethods = {
8
8
 
9
9
  handlers[`${toolPrefix}_list`] = async (args = {}) =>
10
10
  this.asText(await this[listMethod](args));
11
- handlers[`${toolPrefix}_search`] = async (args = {}) =>
12
- this.asText(await this[searchMethod](args));
11
+ if (searchMethod) {
12
+ handlers[`${toolPrefix}_search`] = async (args = {}) =>
13
+ this.asText(await this[searchMethod](args));
14
+ }
13
15
  handlers[`${toolPrefix}_get`] = async (args = {}) =>
14
16
  this.asText(await this.apiClient.get(resource, this.pickRequiredArg(args, idArg)));
15
17
  handlers[`${toolPrefix}_create`] = async (args = {}) =>
@@ -1,93 +1,139 @@
1
1
  export const listingMethods = {
2
- listTests({ page, per_page: perPage, suite_id: suiteId, search_text: searchText, query, ...filters } = {}) {
2
+ listTests({
3
+ page,
4
+ per_page: perPage,
5
+ tql,
6
+ } = {}) {
3
7
  return this.apiClient.list('tests', {
4
8
  page,
5
9
  per_page: perPage,
6
- suite_id: suiteId,
7
- search_text: searchText || query,
8
- ...filters,
10
+ tql,
9
11
  });
10
12
  },
11
13
 
12
- searchTests({ query, search_text: searchText, ...rest } = {}) {
14
+ searchTests({ page, per_page: perPage, tql } = {}) {
13
15
  return this.listTests({
14
- ...rest,
15
- search_text: searchText || query,
16
+ page,
17
+ per_page: perPage,
18
+ tql,
16
19
  });
17
20
  },
18
21
 
19
- listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText, query } = {}) {
22
+ listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText } = {}) {
20
23
  return this.apiClient.list('suites', {
21
24
  page,
22
25
  per_page: perPage,
23
26
  file_type: fileType,
24
27
  tag,
25
28
  labels,
26
- search_text: searchText || query,
29
+ search_text: searchText,
27
30
  });
28
31
  },
29
32
 
30
- searchSuites({ query, search_text: searchText, ...rest } = {}) {
33
+ searchSuites({ search_text: searchText, ...rest } = {}) {
31
34
  return this.listSuites({
32
35
  ...rest,
33
- search_text: searchText || query,
36
+ search_text: searchText,
34
37
  });
35
38
  },
36
39
 
37
- listRuns({ page, per_page: perPage, query } = {}) {
38
- return this.apiClient.list('runs', { page, per_page: perPage, query });
39
- },
40
-
41
- searchRuns({ page, per_page: perPage, query } = {}) {
42
- return this.listRuns({ page, per_page: perPage, query });
43
- },
44
-
45
- listTestruns({ page, per_page: perPage, run_id: runId, query } = {}) {
46
- return this.apiClient.list('testruns', { page, per_page: perPage, run_id: runId, query });
47
- },
48
-
49
- searchTestruns({ page, per_page: perPage, run_id: runId, query } = {}) {
50
- return this.listTestruns({ page, per_page: perPage, run_id: runId, query });
51
- },
52
-
53
- listRungroups({ page, per_page: perPage, query } = {}) {
54
- return this.apiClient.list('rungroups', { page, per_page: perPage, query });
55
- },
56
-
57
- searchRungroups({ page, per_page: perPage, query } = {}) {
58
- return this.listRungroups({ page, per_page: perPage, query });
40
+ listRuns({
41
+ page,
42
+ per_page: perPage,
43
+ tql,
44
+ } = {}) {
45
+ return this.apiClient.list('runs', {
46
+ page,
47
+ per_page: perPage,
48
+ tql,
49
+ });
59
50
  },
60
51
 
61
- listSteps({ page, per_page: perPage, query } = {}) {
62
- return this.apiClient.list('steps', { page, per_page: perPage, query });
52
+ searchRuns({ page, per_page: perPage, tql } = {}) {
53
+ return this.listRuns({ page, per_page: perPage, tql });
54
+ },
55
+
56
+ listTestruns({
57
+ page,
58
+ per_page: perPage,
59
+ run_id: runId,
60
+ test_ids: testIds,
61
+ filter_status: filterStatus,
62
+ filter_kind: filterKind,
63
+ filter_user: filterUser,
64
+ filter_priority: filterPriority,
65
+ filter_substatus: filterSubstatus,
66
+ filter_search: filterSearch,
67
+ filter_message: filterMessage,
68
+ filter_link: filterLink,
69
+ filter_finished_at_date_range: filterFinishedAtDateRange,
70
+ tags,
71
+ labels,
72
+ envs,
73
+ rungroups,
74
+ defects,
75
+ } = {}) {
76
+ return this.apiClient.list('testruns', {
77
+ page,
78
+ per_page: perPage,
79
+ run_id: runId,
80
+ test_ids: Array.isArray(testIds) ? testIds.join(',') : testIds,
81
+ 'filter[status]': filterStatus,
82
+ 'filter[kind]': filterKind,
83
+ 'filter[user]': filterUser,
84
+ 'filter[priority]': filterPriority,
85
+ 'filter[substatus]': filterSubstatus,
86
+ 'filter[search]': filterSearch,
87
+ 'filter[message]': filterMessage,
88
+ 'filter[link]': filterLink,
89
+ 'filter[finished_at_date_range]': filterFinishedAtDateRange,
90
+ tags: Array.isArray(tags) ? tags.join(',') : tags,
91
+ labels: Array.isArray(labels) ? labels.join(',') : labels,
92
+ envs: Array.isArray(envs) ? envs.join(',') : envs,
93
+ rungroups: Array.isArray(rungroups) ? rungroups.join(',') : rungroups,
94
+ defects,
95
+ });
63
96
  },
64
97
 
65
- searchSteps({ page, per_page: perPage, query } = {}) {
66
- return this.listSteps({ page, per_page: perPage, query });
98
+ searchTestruns({ page, per_page: perPage, run_id: runId, filter_search: filterSearch, ...rest } = {}) {
99
+ return this.listTestruns({
100
+ page,
101
+ per_page: perPage,
102
+ run_id: runId,
103
+ filter_search: filterSearch,
104
+ ...rest,
105
+ });
67
106
  },
68
107
 
69
- listSnippets({ page, per_page: perPage, query } = {}) {
70
- return this.apiClient.list('snippets', { page, per_page: perPage, query });
108
+ listRungroups({ page, per_page: perPage } = {}) {
109
+ return this.apiClient.list('rungroups', { page, per_page: perPage });
71
110
  },
72
111
 
73
- searchSnippets({ page, per_page: perPage, query } = {}) {
74
- return this.listSnippets({ page, per_page: perPage, query });
112
+ listSteps({ page, per_page: perPage } = {}) {
113
+ return this.apiClient.list('steps', { page, per_page: perPage });
75
114
  },
76
115
 
77
- listLabels({ page, per_page: perPage, query } = {}) {
78
- return this.apiClient.list('labels', { page, per_page: perPage, query });
116
+ listSnippets({ page, per_page: perPage } = {}) {
117
+ return this.apiClient.list('snippets', { page, per_page: perPage });
79
118
  },
80
119
 
81
- searchLabels({ page, per_page: perPage, query } = {}) {
82
- return this.listLabels({ page, per_page: perPage, query });
120
+ listLabels({ page, per_page: perPage } = {}) {
121
+ return this.apiClient.list('labels', { page, per_page: perPage });
83
122
  },
84
123
 
85
- listPlans({ page, per_page: perPage, query } = {}) {
86
- return this.apiClient.list('plans', { page, per_page: perPage, query });
124
+ listPlans({ page, per_page: perPage, kind, hidden, labels, search_text: searchText } = {}) {
125
+ return this.apiClient.list('plans', {
126
+ page,
127
+ per_page: perPage,
128
+ kind,
129
+ hidden,
130
+ 'labels[]': labels,
131
+ search_text: searchText,
132
+ });
87
133
  },
88
134
 
89
- searchPlans({ page, per_page: perPage, query } = {}) {
90
- return this.listPlans({ page, per_page: perPage, query });
135
+ searchPlans({ page, per_page: perPage, search_text: searchText, ...rest } = {}) {
136
+ return this.listPlans({ page, per_page: perPage, search_text: searchText, ...rest });
91
137
  },
92
138
 
93
139
  listTags() {
@@ -76,7 +76,7 @@ export const payloadMethods = {
76
76
  buildRunCreatePayload({
77
77
  title,
78
78
  description,
79
- plan_id: planId,
79
+ plan_ids: planIds,
80
80
  kind,
81
81
  rungroup_id: rungroupId,
82
82
  env,
@@ -85,13 +85,12 @@ export const payloadMethods = {
85
85
  test_ids: testIds,
86
86
  suite_ids: suiteIds,
87
87
  envs,
88
- configuration,
89
88
  link,
90
89
  } = {}) {
91
90
  return {
92
91
  title,
93
92
  description,
94
- plan_id: planId,
93
+ plan_ids: planIds,
95
94
  kind,
96
95
  rungroup_id: rungroupId,
97
96
  env,
@@ -100,7 +99,6 @@ export const payloadMethods = {
100
99
  test_ids: testIds,
101
100
  suite_ids: suiteIds,
102
101
  envs,
103
- configuration,
104
102
  link,
105
103
  };
106
104
  },
@@ -108,7 +106,6 @@ export const payloadMethods = {
108
106
  buildRunUpdatePayload({
109
107
  title,
110
108
  description,
111
- plan_id: planId,
112
109
  kind,
113
110
  rungroup_id: rungroupId,
114
111
  env,
@@ -117,13 +114,11 @@ export const payloadMethods = {
117
114
  assign_strategy: assignStrategy,
118
115
  test_ids: testIds,
119
116
  suite_ids: suiteIds,
120
- configuration,
121
117
  link,
122
118
  } = {}) {
123
119
  return {
124
120
  title,
125
121
  description,
126
- plan_id: planId,
127
122
  kind,
128
123
  rungroup_id: rungroupId,
129
124
  env,
@@ -132,7 +127,6 @@ export const payloadMethods = {
132
127
  assign_strategy: assignStrategy,
133
128
  test_ids: testIds,
134
129
  suite_ids: suiteIds,
135
- configuration,
136
130
  link,
137
131
  };
138
132
  },