@testomatio/mcp 2.2.0-beta.2 → 2.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -322,6 +322,9 @@ Included tools:
322
322
 
323
323
  - `analytics_tests` - `GET /api/v2/{project_id}/analytics/tests/{kind}`
324
324
  - `analytics_stats` - `GET /api/v2/{project_id}/analytics/stats/{kind}`
325
+ - `analytics_charts_list` - `GET /api/v2/{project_id}/analytics/charts`
326
+ - `analytics_charts_get` - `GET /api/v2/{project_id}/analytics/charts/{id}`
327
+ - `analytics_charts_results` - `GET /api/v2/{project_id}/analytics/charts/{id}/result`
325
328
 
326
329
  Analytics endpoints require the `api_analytics` subscription feature. Use `q` as the TQL filter parameter for analytics tools.
327
330
 
@@ -353,3 +356,19 @@ Example `analytics_stats` call:
353
356
  }
354
357
  }
355
358
  ```
359
+
360
+ `analytics_charts_get` returns a chart definition with its TQL queries; `analytics_charts_results`
361
+ returns per-query totals or the matching tests/runs of one query (by its zero-based `number`).
362
+
363
+ Example `analytics_charts_results` call:
364
+
365
+ ```json
366
+ {
367
+ "name": "analytics_charts_results",
368
+ "arguments": {
369
+ "chart_id": "abc123",
370
+ "number": 2,
371
+ "per_page": 50
372
+ }
373
+ }
374
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "2.2.0-beta.2",
3
+ "version": "2.2.0-beta.4",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -27,8 +27,8 @@ export class TestomatioApiClient {
27
27
  return this.http.request('GET', this.buildPath(resource, id), { query });
28
28
  }
29
29
 
30
- async create(resource, body = {}) {
31
- return this.mutate('POST', this.buildPath(resource), { body });
30
+ async create(resource, body = {}, query = {}) {
31
+ return this.mutate('POST', this.buildPath(resource), { query, body });
32
32
  }
33
33
 
34
34
  async createMultipart(resource, formData, query = {}) {
@@ -39,16 +39,16 @@ export class TestomatioApiClient {
39
39
  return this.mutate('POST', this.buildPath(resource), { query, body });
40
40
  }
41
41
 
42
- async update(resource, id, body = {}) {
43
- return this.mutate('PUT', this.buildPath(resource, id), { body });
42
+ async update(resource, id, body = {}, query = {}) {
43
+ return this.mutate('PUT', this.buildPath(resource, id), { query, body });
44
44
  }
45
45
 
46
- async patch(resource, id, body = {}) {
47
- return this.mutate('PATCH', this.buildPath(resource, id), { body });
46
+ async patch(resource, id, body = {}, query = {}) {
47
+ return this.mutate('PATCH', this.buildPath(resource, id), { query, body });
48
48
  }
49
49
 
50
- async patchMultipart(resource, id, formData) {
51
- return this.mutate('PATCH', this.buildPath(resource, id), { body: formData });
50
+ async patchMultipart(resource, id, formData, query = {}) {
51
+ return this.mutate('PATCH', this.buildPath(resource, id), { query, body: formData });
52
52
  }
53
53
 
54
54
  async delete(resource, id, query = {}) {
@@ -8,6 +8,7 @@ export const ENTITY_CRUD_CONFIGS = [
8
8
  wrapperKey: 'test',
9
9
  createMode: 'wrapped',
10
10
  updateMode: 'wrapped',
11
+ queryArgs: ['branch'],
11
12
  },
12
13
  {
13
14
  toolPrefix: 'suites',
@@ -18,6 +19,7 @@ export const ENTITY_CRUD_CONFIGS = [
18
19
  wrapperKey: 'suite',
19
20
  createMode: 'wrapped',
20
21
  updateMode: 'wrapped',
22
+ queryArgs: ['branch'],
21
23
  },
22
24
  {
23
25
  toolPrefix: 'runs',
@@ -26,6 +28,7 @@ export const ENTITY_CRUD_CONFIGS = [
26
28
  listMethod: 'listRuns',
27
29
  createMode: 'run',
28
30
  updateMode: 'run',
31
+ queryArgs: ['branch'],
29
32
  },
30
33
  {
31
34
  toolPrefix: 'testruns',
@@ -98,4 +101,14 @@ export const ENTITY_CRUD_CONFIGS = [
98
101
  updateMode: 'requirement',
99
102
  updateMethod: 'patch',
100
103
  },
104
+ {
105
+ toolPrefix: 'branches',
106
+ resource: 'branches',
107
+ idArg: 'branch_id',
108
+ listMethod: 'listBranches',
109
+ payloadBuilder: 'buildBranchPayload',
110
+ wrapperKey: 'branch',
111
+ createMode: 'wrapped',
112
+ updateMode: 'wrapped',
113
+ },
101
114
  ];
@@ -0,0 +1,110 @@
1
+ export const BRANCH_PARAM = {
2
+ "type": "string",
3
+ "description": "Branch slug to scope the request to (omit or main for the main branch). For tests and suites, a branch-local record is used when it exists, falling back to main; updating/deleting a main-only record forks an isolated copy into the branch. Runs are tagged with the branch (only reachable with the same branch afterwards). Requires the branches feature."
4
+ };
5
+
6
+ export const BRANCHES_TOOLS = [
7
+ {
8
+ "name": "branches_list",
9
+ "description":
10
+ 'List project branches (/api/v2/{project_id}/branches). Requires the branches feature (enterprise plan). Use filter[state] / filter[title] to narrow down.',
11
+ "inputSchema": {
12
+ "type": "object",
13
+ "properties": {
14
+ "page": {
15
+ "type": "integer",
16
+ "minimum": 1
17
+ },
18
+ "per_page": {
19
+ "type": "integer",
20
+ "minimum": 1,
21
+ "maximum": 100
22
+ },
23
+ "filter_state": {
24
+ "type": "string",
25
+ "enum": [
26
+ "active",
27
+ "merged"
28
+ ],
29
+ "description": "Filter by branch state"
30
+ },
31
+ "filter_title": {
32
+ "type": "string",
33
+ "description": "Filter by title (partial substring match)"
34
+ }
35
+ },
36
+ "additionalProperties": false
37
+ }
38
+ },
39
+ {
40
+ "name": "branches_get",
41
+ "description": "Get branch by slug",
42
+ "inputSchema": {
43
+ "type": "object",
44
+ "properties": {
45
+ "branch_id": {
46
+ "type": "string",
47
+ "description": "Branch slug"
48
+ }
49
+ },
50
+ "required": [
51
+ "branch_id"
52
+ ],
53
+ "additionalProperties": false
54
+ }
55
+ },
56
+ {
57
+ "name": "branches_create",
58
+ "description": "Create branch (/api/v2/{project_id}/branches)",
59
+ "inputSchema": {
60
+ "type": "object",
61
+ "properties": {
62
+ "title": {
63
+ "type": "string",
64
+ "description": "Branch title; a slug is generated from it"
65
+ }
66
+ },
67
+ "required": [
68
+ "title"
69
+ ],
70
+ "additionalProperties": false
71
+ }
72
+ },
73
+ {
74
+ "name": "branches_update",
75
+ "description": "Update branch title (/api/v2/{project_id}/branches/{slug})",
76
+ "inputSchema": {
77
+ "type": "object",
78
+ "properties": {
79
+ "branch_id": {
80
+ "type": "string",
81
+ "description": "Branch slug"
82
+ },
83
+ "title": {
84
+ "type": "string"
85
+ }
86
+ },
87
+ "required": [
88
+ "branch_id"
89
+ ],
90
+ "additionalProperties": false
91
+ }
92
+ },
93
+ {
94
+ "name": "branches_delete",
95
+ "description": "Delete branch by slug",
96
+ "inputSchema": {
97
+ "type": "object",
98
+ "properties": {
99
+ "branch_id": {
100
+ "type": "string",
101
+ "description": "Branch slug"
102
+ }
103
+ },
104
+ "required": [
105
+ "branch_id"
106
+ ],
107
+ "additionalProperties": false
108
+ }
109
+ }
110
+ ];
@@ -1,4 +1,5 @@
1
1
  import { RUNS_TQL_INPUT_DESCRIPTION, RUNS_TQL_REFERENCE } from './tql-reference.js';
2
+ import { BRANCH_PARAM } from './branches.js';
2
3
 
3
4
  export const RUNS_TOOLS = [
4
5
  {
@@ -19,7 +20,8 @@ export const RUNS_TOOLS = [
19
20
  "tql": {
20
21
  "type": "string",
21
22
  "description": RUNS_TQL_INPUT_DESCRIPTION
22
- }
23
+ },
24
+ "branch": BRANCH_PARAM
23
25
  },
24
26
  "additionalProperties": false
25
27
  }
@@ -32,7 +34,8 @@ export const RUNS_TOOLS = [
32
34
  "properties": {
33
35
  "run_id": {
34
36
  "type": "string"
35
- }
37
+ },
38
+ "branch": BRANCH_PARAM
36
39
  },
37
40
  "required": [
38
41
  "run_id"
@@ -135,7 +138,8 @@ export const RUNS_TOOLS = [
135
138
  ],
136
139
  "additionalProperties": false
137
140
  }
138
- }
141
+ },
142
+ "branch": BRANCH_PARAM
139
143
  },
140
144
  "required": [
141
145
  "title"
@@ -240,7 +244,8 @@ export const RUNS_TOOLS = [
240
244
  ],
241
245
  "additionalProperties": false
242
246
  }
243
- }
247
+ },
248
+ "branch": BRANCH_PARAM
244
249
  },
245
250
  "required": [
246
251
  "run_id"
@@ -256,7 +261,8 @@ export const RUNS_TOOLS = [
256
261
  "properties": {
257
262
  "run_id": {
258
263
  "type": "string"
259
- }
264
+ },
265
+ "branch": BRANCH_PARAM
260
266
  },
261
267
  "required": [
262
268
  "run_id"
@@ -1,3 +1,4 @@
1
+ import { BRANCH_PARAM } from './branches.js';
1
2
  export const SUITES_TOOLS = [
2
3
  {
3
4
  "name": "suites_list",
@@ -29,7 +30,8 @@ export const SUITES_TOOLS = [
29
30
  },
30
31
  "search_text": {
31
32
  "type": "string"
32
- }
33
+ },
34
+ "branch": BRANCH_PARAM
33
35
  },
34
36
  "additionalProperties": false
35
37
  }
@@ -42,7 +44,8 @@ export const SUITES_TOOLS = [
42
44
  "properties": {
43
45
  "suite_id": {
44
46
  "type": "string"
45
- }
47
+ },
48
+ "branch": BRANCH_PARAM
46
49
  },
47
50
  "required": [
48
51
  "suite_id"
@@ -120,7 +123,8 @@ export const SUITES_TOOLS = [
120
123
  ],
121
124
  "additionalProperties": false
122
125
  }
123
- }
126
+ },
127
+ "branch": BRANCH_PARAM
124
128
  },
125
129
  "required": [
126
130
  "title"
@@ -201,7 +205,8 @@ export const SUITES_TOOLS = [
201
205
  ],
202
206
  "additionalProperties": false
203
207
  }
204
- }
208
+ },
209
+ "branch": BRANCH_PARAM
205
210
  },
206
211
  "required": [
207
212
  "suite_id"
@@ -217,7 +222,8 @@ export const SUITES_TOOLS = [
217
222
  "properties": {
218
223
  "suite_id": {
219
224
  "type": "string"
220
- }
225
+ },
226
+ "branch": BRANCH_PARAM
221
227
  },
222
228
  "required": [
223
229
  "suite_id"
@@ -1,4 +1,5 @@
1
1
  import { TESTS_TQL_INPUT_DESCRIPTION, TESTS_TQL_REFERENCE } from './tql-reference.js';
2
+ import { BRANCH_PARAM } from './branches.js';
2
3
 
3
4
  export const TESTS_TOOLS = [
4
5
  {
@@ -19,7 +20,8 @@ export const TESTS_TOOLS = [
19
20
  "tql": {
20
21
  "type": "string",
21
22
  "description": TESTS_TQL_INPUT_DESCRIPTION
22
- }
23
+ },
24
+ "branch": BRANCH_PARAM
23
25
  },
24
26
  "additionalProperties": false
25
27
  }
@@ -32,7 +34,8 @@ export const TESTS_TOOLS = [
32
34
  "properties": {
33
35
  "test_id": {
34
36
  "type": "string"
35
- }
37
+ },
38
+ "branch": BRANCH_PARAM
36
39
  },
37
40
  "required": [
38
41
  "test_id"
@@ -116,7 +119,8 @@ export const TESTS_TOOLS = [
116
119
  ],
117
120
  "additionalProperties": false
118
121
  }
119
- }
122
+ },
123
+ "branch": BRANCH_PARAM
120
124
  },
121
125
  "required": [
122
126
  "title",
@@ -207,7 +211,8 @@ export const TESTS_TOOLS = [
207
211
  ],
208
212
  "additionalProperties": false
209
213
  }
210
- }
214
+ },
215
+ "branch": BRANCH_PARAM
211
216
  },
212
217
  "required": [
213
218
  "test_id"
@@ -223,7 +228,8 @@ export const TESTS_TOOLS = [
223
228
  "properties": {
224
229
  "test_id": {
225
230
  "type": "string"
226
- }
231
+ },
232
+ "branch": BRANCH_PARAM
227
233
  },
228
234
  "required": [
229
235
  "test_id"
@@ -18,6 +18,8 @@ const HEAVY_FIELDS_BY_ENTITY = {
18
18
  plans: new Set(['description']),
19
19
  requirements: new Set(['description']),
20
20
  analytics_tests: new Set(['description', 'code', 'cleanTitle', 'publicTitle']),
21
+ analytics_charts: new Set(['description']),
22
+ analytics_chart_results: new Set(['description', 'code', 'cleanTitle', 'publicTitle']),
21
23
  };
22
24
 
23
25
  function isNullish(value) {
@@ -19,17 +19,27 @@ export const handlerMethods = {
19
19
  })
20
20
  );
21
21
  };
22
- handlers[`${toolPrefix}_get`] = async (args = {}) =>
23
- this.asText(await this.apiClient.get(resource, this.pickRequiredArg(args, idArg)));
24
- handlers[`${toolPrefix}_create`] = async (args = {}) =>
25
- this.asText(await this.executeCreate(spec, args));
22
+ handlers[`${toolPrefix}_get`] = async (args = {}) => {
23
+ const id = this.pickRequiredArg(args, idArg);
24
+ return this.asText(
25
+ await this.apiClient.get(resource, id, this.pickQueryArgs(spec, args))
26
+ );
27
+ };
28
+ handlers[`${toolPrefix}_create`] = async (args = {}) => {
29
+ const { query, payloadArgs } = this.splitQueryArgs(spec, args);
30
+ return this.asText(await this.executeCreate(spec, payloadArgs, query));
31
+ };
26
32
  handlers[`${toolPrefix}_update`] = async (args = {}) => {
27
33
  const id = this.pickRequiredArg(args, idArg);
28
- const payloadArgs = this.omitArg(args, idArg);
29
- return this.asText(await this.executeUpdate(spec, id, payloadArgs));
34
+ const { query, payloadArgs } = this.splitQueryArgs(spec, this.omitArgs(args, [idArg]));
35
+ return this.asText(await this.executeUpdate(spec, id, payloadArgs, query));
36
+ };
37
+ handlers[`${toolPrefix}_delete`] = async (args = {}) => {
38
+ const id = this.pickRequiredArg(args, idArg);
39
+ return this.asText(
40
+ await this.apiClient.delete(resource, id, this.pickQueryArgs(spec, args))
41
+ );
30
42
  };
31
- handlers[`${toolPrefix}_delete`] = async (args = {}) =>
32
- this.asText(await this.apiClient.delete(resource, this.pickRequiredArg(args, idArg)));
33
43
  }
34
44
  },
35
45
 
@@ -139,34 +149,58 @@ export const handlerMethods = {
139
149
  return value;
140
150
  },
141
151
 
142
- omitArg(args = {}, key) {
152
+ omitArgs(args = {}, keys) {
143
153
  const payload = { ...args };
144
- delete payload[key];
154
+ for (const key of keys) {
155
+ delete payload[key];
156
+ }
145
157
  return payload;
146
158
  },
147
159
 
148
- executeCreate(spec, args = {}) {
160
+ /**
161
+ * Extract the entity's query args (spec.queryArgs, e.g. branch) from tool args
162
+ * without knowing their names in the handlers.
163
+ */
164
+ splitQueryArgs(spec, args = {}) {
165
+ const queryArgs = spec.queryArgs || [];
166
+ const query = {};
167
+ const payloadArgs = { ...args };
168
+ for (const key of queryArgs) {
169
+ if (args[key] !== undefined) {
170
+ query[key] = args[key];
171
+ delete payloadArgs[key];
172
+ }
173
+ }
174
+ return { query, payloadArgs };
175
+ },
176
+
177
+ pickQueryArgs(spec, args = {}) {
178
+ const { query } = this.splitQueryArgs(spec, args);
179
+ return query;
180
+ },
181
+
182
+ executeCreate(spec, args = {}, query = {}) {
149
183
  if (spec.createMode === 'run') {
150
- return this.createRunWithFallback(args);
184
+ return this.createRunWithFallback(args, query);
151
185
  }
152
186
  if (spec.createMode === 'requirement') {
153
- return this.createRequirement(args);
187
+ return this.createRequirement(args, query);
154
188
  }
155
189
  const payload = this[spec.payloadBuilder](args);
156
- return this.createWrapped(spec.resource, spec.wrapperKey, payload);
190
+ return this.createWrapped(spec.resource, spec.wrapperKey, payload, query);
157
191
  },
158
192
 
159
- executeUpdate(spec, id, args = {}) {
193
+ executeUpdate(spec, id, args = {}, query = {}) {
160
194
  if (spec.updateMode === 'run') {
161
- return this.updateRunWithFallback(id, args);
195
+ return this.updateRunWithFallback(id, args, query);
162
196
  }
163
197
  if (spec.updateMode === 'requirement') {
164
- return this.updateRequirement(id, args);
198
+ return this.updateRequirement(id, args, query);
165
199
  }
166
200
  const payload = this[spec.payloadBuilder](args);
167
201
  if (spec.updateMethod === 'patch') {
168
- return this.patchWrapped(spec.resource, id, spec.wrapperKey, payload);
202
+ return this.patchWrapped(spec.resource, id, spec.wrapperKey, payload, query);
169
203
  }
170
- return this.updateWrapped(spec.resource, id, spec.wrapperKey, payload);
204
+ return this.updateWrapped(spec.resource, id, spec.wrapperKey, payload, query);
171
205
  },
172
206
  };
@@ -6,6 +6,7 @@ export const listingMethods = {
6
6
  count,
7
7
  group_by: groupBy,
8
8
  slim,
9
+ branch,
9
10
  } = {}) {
10
11
  return this.apiClient.list('tests', {
11
12
  page,
@@ -14,10 +15,11 @@ export const listingMethods = {
14
15
  count,
15
16
  group_by: groupBy,
16
17
  slim,
18
+ branch,
17
19
  });
18
20
  },
19
21
 
20
- listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText, count, group_by: groupBy, slim } = {}) {
22
+ listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText, count, group_by: groupBy, slim, branch } = {}) {
21
23
  return this.apiClient.list('suites', {
22
24
  page,
23
25
  per_page: perPage,
@@ -28,6 +30,7 @@ export const listingMethods = {
28
30
  count,
29
31
  group_by: groupBy,
30
32
  slim,
33
+ branch,
31
34
  });
32
35
  },
33
36
 
@@ -38,6 +41,7 @@ export const listingMethods = {
38
41
  count,
39
42
  group_by: groupBy,
40
43
  slim,
44
+ branch,
41
45
  } = {}) {
42
46
  return this.apiClient.list('runs', {
43
47
  page,
@@ -46,6 +50,7 @@ export const listingMethods = {
46
50
  count,
47
51
  group_by: groupBy,
48
52
  slim,
53
+ branch,
49
54
  });
50
55
  },
51
56
 
@@ -139,6 +144,26 @@ export const listingMethods = {
139
144
  return this.apiClient.list('tags', { count, group_by: groupBy, slim });
140
145
  },
141
146
 
147
+ listBranches({
148
+ page,
149
+ per_page: perPage,
150
+ filter_state: filterState,
151
+ filter_title: filterTitle,
152
+ count,
153
+ group_by: groupBy,
154
+ slim,
155
+ } = {}) {
156
+ return this.apiClient.list('branches', {
157
+ page,
158
+ per_page: perPage,
159
+ 'filter[state]': filterState,
160
+ 'filter[title]': filterTitle,
161
+ count,
162
+ group_by: groupBy,
163
+ slim,
164
+ });
165
+ },
166
+
142
167
  getTagByTitle(tagId) {
143
168
  return this.apiClient.get('tags', tagId);
144
169
  },
@@ -2,57 +2,58 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
4
  export const payloadMethods = {
5
- async createWrapped(resource, wrapperKey, payload) {
5
+ async createWrapped(resource, wrapperKey, payload, query = {}) {
6
6
  const wrappedBody = { [wrapperKey]: payload };
7
7
  try {
8
- return await this.apiClient.create(resource, payload);
8
+ return await this.apiClient.create(resource, payload, query);
9
9
  } catch (error) {
10
10
  if (!this.shouldRetryWrappedBody(error, wrapperKey)) {
11
11
  throw error;
12
12
  }
13
- return this.apiClient.create(resource, wrappedBody);
13
+ return this.apiClient.create(resource, wrappedBody, query);
14
14
  }
15
15
  },
16
16
 
17
- async updateWrapped(resource, id, wrapperKey, payload) {
17
+ async updateWrapped(resource, id, wrapperKey, payload, query = {}) {
18
18
  const wrappedBody = { [wrapperKey]: payload };
19
19
  try {
20
- return await this.apiClient.update(resource, id, payload);
20
+ return await this.apiClient.update(resource, id, payload, query);
21
21
  } catch (error) {
22
22
  if (!this.shouldRetryWrappedBody(error, wrapperKey)) {
23
23
  throw error;
24
24
  }
25
- return this.apiClient.update(resource, id, wrappedBody);
25
+ return this.apiClient.update(resource, id, wrappedBody, query);
26
26
  }
27
27
  },
28
28
 
29
- async patchWrapped(resource, id, wrapperKey, payload) {
29
+ async patchWrapped(resource, id, wrapperKey, payload, query = {}) {
30
30
  const wrappedBody = { [wrapperKey]: payload };
31
31
  try {
32
- return await this.apiClient.patch(resource, id, payload);
32
+ return await this.apiClient.patch(resource, id, payload, query);
33
33
  } catch (error) {
34
34
  if (!this.shouldRetryWrappedBody(error, wrapperKey)) {
35
35
  throw error;
36
36
  }
37
- return this.apiClient.patch(resource, id, wrappedBody);
37
+ return this.apiClient.patch(resource, id, wrappedBody, query);
38
38
  }
39
39
  },
40
40
 
41
- async createRequirement(args = {}) {
41
+ async createRequirement(args = {}, query = {}) {
42
42
  const { files, ...payloadArgs } = args;
43
43
  const payload = this.buildRequirementPayload(payloadArgs);
44
44
 
45
45
  if (this.hasFiles(files)) {
46
46
  return this.apiClient.createMultipart(
47
47
  'requirements',
48
- await this.buildRequirementFormData(payload, files)
48
+ await this.buildRequirementFormData(payload, files),
49
+ query
49
50
  );
50
51
  }
51
52
 
52
- return this.createWrapped('requirements', 'requirement', payload);
53
+ return this.createWrapped('requirements', 'requirement', payload, query);
53
54
  },
54
55
 
55
- async updateRequirement(requirementId, args = {}) {
56
+ async updateRequirement(requirementId, args = {}, query = {}) {
56
57
  const { files, ...payloadArgs } = args;
57
58
  const payload = this.buildRequirementPayload(payloadArgs);
58
59
 
@@ -60,11 +61,12 @@ export const payloadMethods = {
60
61
  return this.apiClient.patchMultipart(
61
62
  'requirements',
62
63
  requirementId,
63
- await this.buildRequirementFormData(payload, files)
64
+ await this.buildRequirementFormData(payload, files),
65
+ query
64
66
  );
65
67
  }
66
68
 
67
- return this.patchWrapped('requirements', requirementId, 'requirement', payload);
69
+ return this.patchWrapped('requirements', requirementId, 'requirement', payload, query);
68
70
  },
69
71
 
70
72
  hasFiles(files) {
@@ -198,27 +200,27 @@ export const payloadMethods = {
198
200
  };
199
201
  },
200
202
 
201
- async createRunWithFallback(args = {}) {
203
+ async createRunWithFallback(args = {}, query = {}) {
202
204
  const payload = this.buildRunCreatePayload(args);
203
205
  try {
204
- return await this.apiClient.create('runs', payload);
206
+ return await this.apiClient.create('runs', payload, query);
205
207
  } catch (error) {
206
208
  if (!this.shouldRetryWrappedBody(error, 'run')) {
207
209
  throw error;
208
210
  }
209
- return this.apiClient.create('runs', { run: payload });
211
+ return this.apiClient.create('runs', { run: payload }, query);
210
212
  }
211
213
  },
212
214
 
213
- async updateRunWithFallback(runId, args = {}) {
215
+ async updateRunWithFallback(runId, args = {}, query = {}) {
214
216
  const payload = this.buildRunUpdatePayload(args);
215
217
  try {
216
- return await this.apiClient.update('runs', runId, payload);
218
+ return await this.apiClient.update('runs', runId, payload, query);
217
219
  } catch (error) {
218
220
  if (!this.shouldRetryWrappedBody(error, 'run')) {
219
221
  throw error;
220
222
  }
221
- return this.apiClient.update('runs', runId, { run: payload });
223
+ return this.apiClient.update('runs', runId, { run: payload }, query);
222
224
  }
223
225
  },
224
226
 
@@ -351,4 +353,8 @@ export const payloadMethods = {
351
353
  confluence_url: confluenceUrl,
352
354
  };
353
355
  },
356
+
357
+ buildBranchPayload({ title } = {}) {
358
+ return { title };
359
+ },
354
360
  };
@@ -14,6 +14,7 @@ import { ISSUES_TOOLS } from './definitions/issues.js';
14
14
  import { PLANS_TOOLS } from './definitions/plans.js';
15
15
  import { REQUIREMENTS_TOOLS } from './definitions/requirements.js';
16
16
  import { ATTACHMENT_TOOLS } from './definitions/attachments.js';
17
+ import { BRANCHES_TOOLS } from './definitions/branches.js';
17
18
  import { withListOptions, withCountGroupOptions } from './list-projection.js';
18
19
 
19
20
  export const TOOL_DEFINITIONS = withCountGroupOptions(withListOptions([
@@ -33,4 +34,5 @@ export const TOOL_DEFINITIONS = withCountGroupOptions(withListOptions([
33
34
  ...ATTACHMENT_TOOLS,
34
35
  ...PLANS_TOOLS,
35
36
  ...REQUIREMENTS_TOOLS,
37
+ ...BRANCHES_TOOLS,
36
38
  ]));
@@ -17,7 +17,7 @@ function withPagination(payload) {
17
17
  if (!Array.isArray(payload.data) || !meta || typeof meta !== 'object') {
18
18
  return payload;
19
19
  }
20
- const { total, page, per_page: perPage } = meta;
20
+ const { total, page, per_page: perPage, has_more: backendHasMore } = meta;
21
21
  if (
22
22
  typeof total !== 'number' ||
23
23
  typeof page !== 'number' ||
@@ -26,7 +26,10 @@ function withPagination(payload) {
26
26
  ) {
27
27
  return payload;
28
28
  }
29
- if (page * perPage < total) {
29
+ const hasMore =
30
+ typeof backendHasMore === 'boolean' ? backendHasMore : page * perPage < total;
31
+
32
+ if (hasMore) {
30
33
  return {
31
34
  ...payload,
32
35
  _note: `Showing ${payload.data.length} of ${total} (page ${page}). More available — refine the filter or request the next page.`,