@smartbear/mcp 0.8.0 → 0.10.0

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 (66) hide show
  1. package/README.md +29 -4
  2. package/dist/api-hub/client/api.js +239 -10
  3. package/dist/api-hub/client/configuration.js +5 -0
  4. package/dist/api-hub/client/index.js +1 -0
  5. package/dist/api-hub/client/portal-types.js +126 -0
  6. package/dist/api-hub/client/registry-types.js +8 -0
  7. package/dist/api-hub/client/tools.js +78 -15
  8. package/dist/api-hub/client/user-management-types.js +24 -0
  9. package/dist/api-hub/client.js +34 -0
  10. package/dist/bugsnag/client/api/CurrentUser.js +12 -49
  11. package/dist/bugsnag/client/api/Error.js +29 -142
  12. package/dist/bugsnag/client/api/Project.js +52 -113
  13. package/dist/bugsnag/client/api/api.js +3743 -0
  14. package/dist/bugsnag/client/api/base.js +97 -34
  15. package/dist/bugsnag/client/api/configuration.js +26 -0
  16. package/dist/bugsnag/client/api/index.js +2 -0
  17. package/dist/bugsnag/client/filters.js +28 -0
  18. package/dist/bugsnag/client.js +104 -155
  19. package/dist/collaborator/client.js +364 -0
  20. package/dist/common/server.js +73 -23
  21. package/dist/common/types.js +6 -1
  22. package/dist/index.js +9 -1
  23. package/dist/pactflow/client/prompt-utils.js +2 -1
  24. package/dist/pactflow/client/tools.js +4 -4
  25. package/dist/pactflow/client/utils.js +5 -4
  26. package/dist/pactflow/client.js +10 -9
  27. package/dist/qmetry/client/api/client-api.js +21 -16
  28. package/dist/qmetry/client/api/error-handler.js +329 -0
  29. package/dist/qmetry/client/auto-resolve.js +96 -0
  30. package/dist/qmetry/client/handlers.js +33 -2
  31. package/dist/qmetry/client/issues.js +123 -0
  32. package/dist/qmetry/client/project.js +73 -0
  33. package/dist/qmetry/client/requirement.js +76 -0
  34. package/dist/qmetry/client/testcase.js +122 -6
  35. package/dist/qmetry/client/testsuite.js +272 -0
  36. package/dist/qmetry/client/tools/index.js +17 -0
  37. package/dist/qmetry/client/tools/issue-tools.js +545 -0
  38. package/dist/qmetry/client/tools/project-tools.js +348 -0
  39. package/dist/qmetry/client/tools/requirement-tools.js +530 -0
  40. package/dist/qmetry/client/tools/testcase-tools.js +526 -0
  41. package/dist/qmetry/client/tools/testsuite-tools.js +772 -0
  42. package/dist/qmetry/client/tools/types.js +1 -0
  43. package/dist/qmetry/client/utils.js +16 -0
  44. package/dist/qmetry/client.js +27 -17
  45. package/dist/qmetry/config/constants.js +28 -0
  46. package/dist/qmetry/config/rest-endpoints.js +30 -0
  47. package/dist/qmetry/types/common.js +599 -9
  48. package/dist/qmetry/types/issues.js +16 -0
  49. package/dist/qmetry/types/project.js +17 -0
  50. package/dist/qmetry/types/requirements.js +19 -0
  51. package/dist/qmetry/types/testcase.js +20 -0
  52. package/dist/qmetry/types/testsuite.js +44 -0
  53. package/dist/reflect/client.js +7 -6
  54. package/dist/zephyr/client.js +7 -1
  55. package/dist/zephyr/common/api-client.js +8 -0
  56. package/dist/zephyr/common/auth-service.js +1 -0
  57. package/dist/zephyr/common/rest-api-schemas.js +5173 -0
  58. package/dist/zephyr/tool/project/get-project.js +39 -0
  59. package/dist/zephyr/tool/project/get-projects.js +7 -13
  60. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +72 -0
  61. package/package.json +1 -1
  62. package/dist/bugsnag/client/api/filters.js +0 -167
  63. package/dist/bugsnag/client/configuration.js +0 -10
  64. package/dist/bugsnag/client/index.js +0 -2
  65. package/dist/qmetry/client/tools.js +0 -222
  66. package/dist/zephyr/common/types.js +0 -35
@@ -1,9 +1,9 @@
1
1
  import NodeCache from "node-cache";
2
2
  import { z } from "zod";
3
3
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
4
- import { FilterObjectSchema, toQueryString, } from "./client/api/filters.js";
5
- import { ProjectAPI, } from "./client/api/Project.js";
6
- import { Configuration, CurrentUserAPI, ErrorAPI } from "./client/index.js";
4
+ import { ToolError, } from "../common/types.js";
5
+ import { Configuration, CurrentUserAPI, ErrorAPI, ProjectAPI, } from "./client/api/index.js";
6
+ import { FilterObjectSchema, toUrlSearchParams, } from "./client/filters.js";
7
7
  const HUB_PREFIX = "00000";
8
8
  const DEFAULT_DOMAIN = "bugsnag.com";
9
9
  const HUB_DOMAIN = "bugsnag.smartbear.com";
@@ -39,7 +39,7 @@ export class BugsnagClient {
39
39
  this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
40
40
  this.appEndpoint = this.getEndpoint("app", projectApiKey, endpoint);
41
41
  const config = new Configuration({
42
- authToken: token,
42
+ apiKey: `token ${token}`,
43
43
  headers: {
44
44
  "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
45
45
  "Content-Type": "application/json",
@@ -121,15 +121,15 @@ export class BugsnagClient {
121
121
  async getDashboardUrl(project) {
122
122
  return `${this.appEndpoint}/${(await this.getOrganization()).slug}/${project.slug}`;
123
123
  }
124
- async getErrorUrl(project, errorId, queryString = "") {
124
+ async getErrorUrl(project, errorId, queryString) {
125
125
  const dashboardUrl = await this.getDashboardUrl(project);
126
- return `${dashboardUrl}/errors/${errorId}${queryString}`;
126
+ return `${dashboardUrl}/errors/${errorId}${queryString ? `?${queryString}` : ""}`;
127
127
  }
128
128
  async getOrganization() {
129
129
  let org = this.cache.get(cacheKeys.ORG);
130
130
  if (!org) {
131
131
  const response = await this.currentUserApi.listUserOrganizations();
132
- const orgs = response.body || [];
132
+ const orgs = response.body;
133
133
  if (!orgs || orgs.length === 0) {
134
134
  throw new Error("No organizations found for the current user.");
135
135
  }
@@ -147,7 +147,7 @@ export class BugsnagClient {
147
147
  if (!projects) {
148
148
  const org = await this.getOrganization();
149
149
  const response = await this.currentUserApi.getOrganizationProjects(org.id);
150
- projects = response.body || [];
150
+ projects = response.body;
151
151
  this.cache.set(cacheKeys.PROJECTS, projects);
152
152
  }
153
153
  return projects;
@@ -160,9 +160,10 @@ export class BugsnagClient {
160
160
  let project = this.cache.get(cacheKeys.CURRENT_PROJECT) ?? null;
161
161
  if (!project && this.projectApiKey) {
162
162
  const projects = await this.getProjects();
163
- project = projects.find((p) => p.api_key === this.projectApiKey) ?? null;
163
+ project =
164
+ projects.find((p) => p.apiKey === this.projectApiKey) ?? null;
164
165
  if (!project) {
165
- throw new Error("Unable to find project with the configured API key.");
166
+ throw new ToolError("Unable to find project with the configured API key.");
166
167
  }
167
168
  this.cache.set(cacheKeys.CURRENT_PROJECT, project);
168
169
  if (project) {
@@ -174,9 +175,9 @@ export class BugsnagClient {
174
175
  async getProjectEventFilters(project) {
175
176
  let filtersResponse = (await this.projectApi.listProjectEventFields(project.id)).body;
176
177
  if (!filtersResponse || filtersResponse.length === 0) {
177
- throw new Error(`No event fields found for project ${project.name}.`);
178
+ throw new ToolError(`No event fields found for project ${project.name}.`);
178
179
  }
179
- filtersResponse = filtersResponse.filter((field) => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
180
+ filtersResponse = filtersResponse.filter((field) => field.displayId && !EXCLUDED_EVENT_FIELDS.has(field.displayId));
180
181
  return filtersResponse;
181
182
  }
182
183
  async getEvent(eventId, projectId) {
@@ -186,111 +187,48 @@ export class BugsnagClient {
186
187
  const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch((_e) => null)));
187
188
  return projectEvents.find((event) => event && !!event.body)?.body || null;
188
189
  }
189
- async updateError(projectId, errorId, operation, options) {
190
- const errorUpdateRequest = {
191
- operation: operation,
192
- ...options,
193
- };
194
- const response = await this.errorsApi.updateErrorOnProject(projectId, errorId, errorUpdateRequest);
195
- return response.status === 200 || response.status === 204;
196
- }
197
190
  async getInputProject(projectId) {
198
191
  if (typeof projectId === "string") {
199
192
  const maybeProject = await this.getProject(projectId);
200
193
  if (!maybeProject) {
201
- throw new Error(`Project with ID ${projectId} not found.`);
194
+ throw new ToolError(`Project with ID ${projectId} not found.`);
202
195
  }
203
196
  return maybeProject;
204
197
  }
205
198
  else {
206
199
  const currentProject = await this.getCurrentProject();
207
200
  if (!currentProject) {
208
- throw new Error("No current project found. Please provide a projectId or configure a project API key.");
201
+ throw new ToolError("No current project found. Please provide a projectId or configure a project API key.");
209
202
  }
210
203
  return currentProject;
211
204
  }
212
205
  }
213
- async listBuilds(projectId, opts) {
214
- const response = await this.projectApi.listBuilds(projectId, opts);
215
- if (!response.body || response.body.length === 0) {
216
- return { ...response, body: [] };
217
- }
218
- const project = await this.getProject(projectId);
219
- if (!project)
220
- throw new Error(`Project with ID ${projectId} not found.`);
221
- return {
222
- ...response,
223
- body: response.body.map((b) => this.addStabilityData(b, project)),
224
- };
225
- }
226
- async getBuild(projectId, buildId) {
227
- const response = await this.projectApi.getBuild(projectId, buildId);
228
- if (!response.body)
229
- throw new Error(`No build for ${buildId} found.`);
230
- const project = await this.getProject(projectId);
231
- if (!project)
232
- throw new Error(`Project with ID ${projectId} not found.`);
233
- return { ...response, body: this.addStabilityData(response.body, project) };
234
- }
235
- async listReleases(projectId, opts) {
236
- const response = await this.projectApi.listReleases(projectId, opts);
237
- if (!response.body || response.body.length === 0) {
238
- return { ...response, body: [] };
239
- }
240
- const project = await this.getProject(projectId);
241
- if (!project)
242
- throw new Error(`Project with ID ${projectId} not found.`);
243
- return {
244
- ...response,
245
- body: response.body.map((r) => this.addStabilityData(r, project)),
246
- };
247
- }
248
- async getRelease(projectId, releaseId) {
249
- const response = await this.projectApi.getRelease(releaseId);
250
- if (!response.body)
251
- throw new Error(`No release for ${releaseId} found.`);
252
- const project = await this.getProject(projectId);
253
- if (!project)
254
- throw new Error(`Project with ID ${projectId} not found.`);
255
- return { ...response, body: this.addStabilityData(response.body, project) };
256
- }
257
- async listBuildsInRelease(projectId, releaseId) {
258
- const response = await this.projectApi.listBuildsInRelease(releaseId);
259
- if (!response.body || response.body.length === 0) {
260
- return { ...response, body: [] };
261
- }
262
- const project = await this.getProject(projectId);
263
- if (!project)
264
- throw new Error(`Project with ID ${projectId} not found.`);
265
- return {
266
- ...response,
267
- body: response.body.map((b) => this.addStabilityData(b, project)),
268
- };
269
- }
270
206
  addStabilityData(source, project) {
271
- const user_stability = source.accumulative_daily_users_seen === 0 // avoid division by zero
207
+ const accumulativeDailyUsersSeen = source.accumulativeDailyUsersSeen || 0;
208
+ const accumulativeDailyUsersWithUnhandled = source.accumulativeDailyUsersWithUnhandled || 0;
209
+ const userStability = accumulativeDailyUsersSeen === 0 // avoid division by zero
272
210
  ? 0
273
- : (source.accumulative_daily_users_seen -
274
- source.accumulative_daily_users_with_unhandled) /
275
- source.accumulative_daily_users_seen;
276
- const session_stability = source.total_sessions_count === 0 // avoid division by zero
211
+ : (accumulativeDailyUsersSeen - accumulativeDailyUsersWithUnhandled) /
212
+ accumulativeDailyUsersSeen;
213
+ const totalSessionsCount = source.totalSessionsCount || 0;
214
+ const unhandledSessionsCount = source.unhandledSessionsCount || 0;
215
+ const sessionStability = totalSessionsCount === 0 // avoid division by zero
277
216
  ? 0
278
- : (source.total_sessions_count - source.unhandled_sessions_count) /
279
- source.total_sessions_count;
280
- const stabilityMetric = project.stability_target_type === "user"
281
- ? user_stability
282
- : session_stability;
283
- const meets_target_stability = stabilityMetric >= project.target_stability.value;
284
- const meets_critical_stability = stabilityMetric >= project.critical_stability.value;
217
+ : (totalSessionsCount - unhandledSessionsCount) / totalSessionsCount;
218
+ const stabilityMetric = project.stabilityTargetType === "user" ? userStability : sessionStability;
219
+ const targetStability = project.targetStability?.value || 0;
220
+ const criticalStability = project.criticalStability?.value || 0;
221
+ const meetsTargetStability = stabilityMetric >= targetStability;
222
+ const meetsCriticalStability = stabilityMetric >= criticalStability;
285
223
  return {
286
224
  ...source,
287
- user_stability,
288
- session_stability,
289
- stability_target_type: project.stability_target_type,
290
- target_stability: project.target_stability.value,
291
- critical_stability: project.critical_stability.value,
292
- meets_target_stability,
293
- meets_critical_stability,
225
+ userStability,
226
+ sessionStability,
227
+ stabilityTargetType: project.stabilityTargetType || "user",
228
+ targetStability,
229
+ criticalStability,
230
+ meetsTargetStability,
231
+ meetsCriticalStability,
294
232
  };
295
233
  }
296
234
  registerTools(register, getInput) {
@@ -406,7 +344,7 @@ export class BugsnagClient {
406
344
  ],
407
345
  },
408
346
  ],
409
- outputFormat: "JSON object containing: " +
347
+ outputDescription: "JSON object containing: " +
410
348
  " - error_details: Aggregated data about the error, including first and last seen occurrence" +
411
349
  " - latest_event: Detailed information about the most recent occurrence of the error, including stacktrace, breadcrumbs, user and context" +
412
350
  " - pivots: List of pivots (summaries) for the error, which can be used to analyze patterns in occurrences" +
@@ -429,28 +367,22 @@ export class BugsnagClient {
429
367
  }, async (args, _extra) => {
430
368
  const project = await this.getInputProject(args.projectId);
431
369
  if (!args.errorId)
432
- throw new Error("Both projectId and errorId arguments are required");
370
+ throw new ToolError("Both projectId and errorId arguments are required");
433
371
  const errorDetails = (await this.errorsApi.viewErrorOnProject(project.id, args.errorId)).body;
434
372
  if (!errorDetails) {
435
- throw new Error(`Error with ID ${args.errorId} not found in project ${project.id}.`);
373
+ throw new ToolError(`Error with ID ${args.errorId} not found in project ${project.id}.`);
436
374
  }
437
- // Build query parameters
438
- const params = new URLSearchParams();
439
- // Add sorting and pagination parameters to get the latest event
440
- params.append("sort", "timestamp");
441
- params.append("direction", "desc");
442
- params.append("per_page", "1");
443
- params.append("full_reports", "true");
444
375
  const filters = {
445
376
  error: [{ type: "eq", value: args.errorId }],
446
377
  ...args.filters,
447
378
  };
448
- const filtersQueryString = toQueryString(filters);
449
- const listEventsQueryString = `?${params}&${filtersQueryString}`;
450
379
  // Get the latest event for this error using the events endpoint with filters
451
380
  let latestEvent = null;
452
381
  try {
453
- latestEvent = (await this.errorsApi.getLatestEventOnProject(project.id, listEventsQueryString)).body;
382
+ const latestEvents = (await this.errorsApi.listEventsOnProject(project.id, null, "timestamp", "desc", 1, filters, true)).body;
383
+ if (latestEvents && latestEvents.length > 0) {
384
+ latestEvent = latestEvents[0];
385
+ }
454
386
  }
455
387
  catch (e) {
456
388
  console.warn("Failed to fetch latest event:", e);
@@ -459,9 +391,8 @@ export class BugsnagClient {
459
391
  const content = {
460
392
  error_details: errorDetails,
461
393
  latest_event: latestEvent,
462
- pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId))
463
- .body || [],
464
- url: await this.getErrorUrl(project, args.errorId, `?${filtersQueryString}`),
394
+ pivots: (await this.errorsApi.getPivotValuesOnAnError(project.id, args.errorId, filters, 5)).body || [],
395
+ url: await this.getErrorUrl(project, args.errorId, toUrlSearchParams(filters).toString()),
465
396
  };
466
397
  return {
467
398
  content: [{ type: "text", text: JSON.stringify(content) }],
@@ -505,17 +436,17 @@ export class BugsnagClient {
505
436
  ],
506
437
  }, async (args, _extra) => {
507
438
  if (!args.link)
508
- throw new Error("link argument is required");
439
+ throw new ToolError("link argument is required");
509
440
  const url = new URL(args.link);
510
441
  const eventId = url.searchParams.get("event_id");
511
442
  const projectSlug = url.pathname.split("/")[2];
512
443
  if (!projectSlug || !eventId)
513
- throw new Error("Both projectSlug and eventId must be present in the link");
444
+ throw new ToolError("Both projectSlug and eventId must be present in the link");
514
445
  // get the project id from list of projects
515
446
  const projects = await this.getProjects();
516
447
  const projectId = projects.find((p) => p.slug === projectSlug)?.id;
517
448
  if (!projectId) {
518
- throw new Error("Project with the specified slug not found.");
449
+ throw new ToolError("Project with the specified slug not found.");
519
450
  }
520
451
  const response = await this.getEvent(eventId, projectId);
521
452
  return {
@@ -578,7 +509,7 @@ export class BugsnagClient {
578
509
  },
579
510
  {
580
511
  name: "nextUrl",
581
- type: z.string().url(),
512
+ type: z.string(),
582
513
  description: "URL for retrieving the next page of results. Use the value in the previous response to get the next page when more results are available.",
583
514
  required: false,
584
515
  examples: [
@@ -648,29 +579,19 @@ export class BugsnagClient {
648
579
  // Validate filter keys against cached event fields
649
580
  if (args.filters) {
650
581
  const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
651
- const validKeys = new Set(eventFields.map((f) => f.display_id));
582
+ const validKeys = new Set(eventFields.map((f) => f.displayId));
652
583
  for (const key of Object.keys(args.filters)) {
653
584
  if (!validKeys.has(key)) {
654
- throw new Error(`Invalid filter key: ${key}`);
585
+ throw new ToolError(`Invalid filter key: ${key}`);
655
586
  }
656
587
  }
657
588
  }
658
- const defaultFilters = {
589
+ const filters = {
659
590
  "event.since": [{ type: "eq", value: "30d" }],
660
591
  "error.status": [{ type: "eq", value: "open" }],
592
+ ...args.filters,
661
593
  };
662
- const options = {
663
- filters: { ...defaultFilters, ...args.filters },
664
- };
665
- if (args.sort !== undefined)
666
- options.sort = args.sort;
667
- if (args.direction !== undefined)
668
- options.direction = args.direction;
669
- if (args.perPage !== undefined)
670
- options.per_page = args.perPage;
671
- if (args.nextUrl !== undefined)
672
- options.next_url = args.nextUrl;
673
- const response = await this.errorsApi.listProjectErrors(project.id, options);
594
+ const response = await this.errorsApi.listProjectErrors(project.id, null, args.sort || "last_seen", args.direction || "desc", args.perPage || 30, filters, args.nextUrl);
674
595
  const result = {
675
596
  data: response.body,
676
597
  next_url: response.nextUrl ?? undefined,
@@ -705,7 +626,7 @@ export class BugsnagClient {
705
626
  }, async (_args, _extra) => {
706
627
  const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
707
628
  if (!projectFields)
708
- throw new Error("No event filters found in cache.");
629
+ throw new ToolError("No event filters found in cache.");
709
630
  return {
710
631
  content: [{ type: "text", text: JSON.stringify(projectFields) }],
711
632
  };
@@ -784,12 +705,18 @@ export class BugsnagClient {
784
705
  severity = result.content.severity;
785
706
  }
786
707
  }
787
- const result = await this.updateError(project.id, errorId, operation, {
788
- severity,
708
+ const result = await this.errorsApi.updateErrorOnProject(project.id, errorId, {
709
+ operation: operation,
710
+ severity: severity,
789
711
  });
790
712
  return {
791
713
  content: [
792
- { type: "text", text: JSON.stringify({ success: result }) },
714
+ {
715
+ type: "text",
716
+ text: JSON.stringify({
717
+ success: result.status === 200 || result.status === 204,
718
+ }),
719
+ },
793
720
  ],
794
721
  };
795
722
  });
@@ -826,6 +753,13 @@ export class BugsnagClient {
826
753
  required: false,
827
754
  examples: ["true", "false"],
828
755
  },
756
+ {
757
+ name: "perPage",
758
+ type: z.number().min(1).max(100).default(30),
759
+ description: "How many results to return per page.",
760
+ required: false,
761
+ examples: ["30", "50", "100"],
762
+ },
829
763
  {
830
764
  name: "nextUrl",
831
765
  type: z.string(),
@@ -864,21 +798,23 @@ export class BugsnagClient {
864
798
  ],
865
799
  readOnly: true,
866
800
  idempotent: true,
867
- outputFormat: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
801
+ outputDescription: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
868
802
  }, async (args, _extra) => {
869
- const response = await this.listReleases((await this.getInputProject(args.projectId)).id, {
870
- release_stage_name: args.releaseStage,
871
- visible_only: args.visibleOnly,
872
- next_url: args.nextUrl,
873
- });
803
+ const project = await this.getInputProject(args.projectId);
804
+ const response = await this.projectApi.listProjectReleaseGroups(project.id, args.releaseStage || "production", false, // Not top-only
805
+ args.visibleOnly || false, args.perPage || 30, args.nextUrl);
806
+ let releases = [];
807
+ if (response.body) {
808
+ releases = response.body.map((r) => this.addStabilityData(r, project));
809
+ }
874
810
  return {
875
811
  content: [
876
812
  {
877
813
  type: "text",
878
814
  text: JSON.stringify({
879
- data: response.body,
815
+ data: releases,
880
816
  next_url: response.nextUrl ?? undefined,
881
- data_count: response.body?.length,
817
+ data_count: releases.length,
882
818
  total_count: response.totalCount ?? undefined,
883
819
  }),
884
820
  },
@@ -925,20 +861,29 @@ export class BugsnagClient {
925
861
  hints: ["Release IDs can be found using the List releases tool"],
926
862
  readOnly: true,
927
863
  idempotent: true,
928
- outputFormat: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
864
+ outputDescription: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
929
865
  }, async (args, _extra) => {
930
866
  if (!args.releaseId)
931
- throw new Error("releaseId argument is required");
867
+ throw new ToolError("releaseId argument is required");
932
868
  const project = await this.getInputProject(args.projectId);
933
- const releaseResponse = await this.getRelease(project.id, args.releaseId);
934
- const buildsResponse = await this.listBuildsInRelease(project.id, args.releaseId);
869
+ const releaseResponse = await this.projectApi.getReleaseGroup(args.releaseId);
870
+ if (!releaseResponse.body)
871
+ throw new ToolError(`No release for ${args.releaseId} found.`);
872
+ const release = this.addStabilityData(releaseResponse.body, project);
873
+ let builds = [];
874
+ if (releaseResponse.body) {
875
+ const buildsResponse = await this.projectApi.listBuildsInRelease(args.releaseId);
876
+ if (buildsResponse.body) {
877
+ builds = buildsResponse.body.map((b) => this.addStabilityData(b, project));
878
+ }
879
+ }
935
880
  return {
936
881
  content: [
937
882
  {
938
883
  type: "text",
939
884
  text: JSON.stringify({
940
- release: releaseResponse.body,
941
- builds: buildsResponse.body,
885
+ release: release,
886
+ builds: builds,
942
887
  }),
943
888
  },
944
889
  ],
@@ -984,13 +929,17 @@ export class BugsnagClient {
984
929
  hints: ["Build IDs can be found using the List builds tool"],
985
930
  readOnly: true,
986
931
  idempotent: true,
987
- outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
932
+ outputDescription: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
988
933
  }, async (args, _extra) => {
989
934
  if (!args.buildId)
990
- throw new Error("buildId argument is required");
991
- const response = await this.getBuild((await this.getInputProject(args.projectId)).id, args.buildId);
935
+ throw new ToolError("buildId argument is required");
936
+ const project = await this.getInputProject(args.projectId);
937
+ const response = await this.projectApi.getProjectReleaseById(project.id, args.buildId);
938
+ if (!response.body)
939
+ throw new ToolError(`No build for ${args.buildId} found.`);
940
+ const build = this.addStabilityData(response.body, project);
992
941
  return {
993
- content: [{ type: "text", text: JSON.stringify(response.body) }],
942
+ content: [{ type: "text", text: JSON.stringify(build) }],
994
943
  };
995
944
  });
996
945
  }