@smartbear/mcp 0.7.0 → 0.9.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 (52) hide show
  1. package/README.md +19 -2
  2. package/dist/api-hub/client/api.js +198 -23
  3. package/dist/api-hub/client/registry-types.js +22 -0
  4. package/dist/api-hub/client/tools.js +19 -1
  5. package/dist/api-hub/client.js +9 -0
  6. package/dist/bugsnag/client/api/CurrentUser.js +13 -50
  7. package/dist/bugsnag/client/api/Error.js +30 -155
  8. package/dist/bugsnag/client/api/Project.js +56 -124
  9. package/dist/bugsnag/client/api/api.js +3743 -0
  10. package/dist/bugsnag/client/api/base.js +107 -32
  11. package/dist/bugsnag/client/api/configuration.js +26 -0
  12. package/dist/bugsnag/client/api/index.js +2 -0
  13. package/dist/bugsnag/client/filters.js +28 -0
  14. package/dist/bugsnag/client.js +157 -325
  15. package/dist/common/server.js +29 -4
  16. package/dist/common/types.js +6 -1
  17. package/dist/index.js +8 -1
  18. package/dist/pactflow/client/prompt-utils.js +2 -1
  19. package/dist/pactflow/client/tools.js +9 -9
  20. package/dist/pactflow/client/utils.js +5 -4
  21. package/dist/pactflow/client.js +16 -14
  22. package/dist/qmetry/client/api/client-api.js +21 -16
  23. package/dist/qmetry/client/api/error-handler.js +329 -0
  24. package/dist/qmetry/client/auto-resolve.js +74 -0
  25. package/dist/qmetry/client/handlers.js +19 -2
  26. package/dist/qmetry/client/issues.js +26 -0
  27. package/dist/qmetry/client/project.js +56 -0
  28. package/dist/qmetry/client/requirement.js +76 -0
  29. package/dist/qmetry/client/testcase.js +46 -8
  30. package/dist/qmetry/client/testsuite.js +117 -0
  31. package/dist/qmetry/client/tools.js +1455 -4
  32. package/dist/qmetry/client/utils.js +16 -0
  33. package/dist/qmetry/client.js +19 -16
  34. package/dist/qmetry/config/constants.js +14 -0
  35. package/dist/qmetry/config/rest-endpoints.js +20 -0
  36. package/dist/qmetry/types/common.js +313 -8
  37. package/dist/qmetry/types/issues.js +6 -0
  38. package/dist/qmetry/types/project.js +10 -0
  39. package/dist/qmetry/types/requirements.js +19 -0
  40. package/dist/qmetry/types/testcase.js +14 -0
  41. package/dist/qmetry/types/testsuite.js +26 -0
  42. package/dist/reflect/client.js +7 -6
  43. package/dist/zephyr/client.js +16 -0
  44. package/dist/zephyr/common/api-client.js +27 -0
  45. package/dist/zephyr/common/auth-service.js +15 -0
  46. package/dist/zephyr/common/types.js +35 -0
  47. package/dist/zephyr/tool/project/get-projects.js +54 -0
  48. package/dist/zephyr/tool/zephyr-tool.js +1 -0
  49. package/package.json +3 -2
  50. package/dist/bugsnag/client/api/filters.js +0 -167
  51. package/dist/bugsnag/client/configuration.js +0 -10
  52. package/dist/bugsnag/client/index.js +0 -2
@@ -1,10 +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 { getNextUrlPathFromHeader } from "./client/api/base.js";
5
- import { FilterObjectSchema, toQueryString, } from "./client/api/filters.js";
6
- import { ProjectAPI, } from "./client/api/Project.js";
7
- 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";
8
7
  const HUB_PREFIX = "00000";
9
8
  const DEFAULT_DOMAIN = "bugsnag.com";
10
9
  const HUB_DOMAIN = "bugsnag.smartbear.com";
@@ -13,9 +12,6 @@ const cacheKeys = {
13
12
  PROJECTS: "bugsnag_projects",
14
13
  CURRENT_PROJECT: "bugsnag_current_project",
15
14
  CURRENT_PROJECT_EVENT_FILTERS: "bugsnag_current_project_event_filters",
16
- BUILD: "bugsnag_build", // + buildId
17
- RELEASE: "bugsnag_release", // + releaseId
18
- BUILDS_IN_RELEASE: "bugsnag_builds_in_release", // + releaseId
19
15
  };
20
16
  // Exclude certain event fields from the project event filters to improve agent usage
21
17
  const EXCLUDED_EVENT_FIELDS = new Set([
@@ -43,7 +39,7 @@ export class BugsnagClient {
43
39
  this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
44
40
  this.appEndpoint = this.getEndpoint("app", projectApiKey, endpoint);
45
41
  const config = new Configuration({
46
- authToken: token,
42
+ apiKey: `token ${token}`,
47
43
  headers: {
48
44
  "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
49
45
  "Content-Type": "application/json",
@@ -125,15 +121,15 @@ export class BugsnagClient {
125
121
  async getDashboardUrl(project) {
126
122
  return `${this.appEndpoint}/${(await this.getOrganization()).slug}/${project.slug}`;
127
123
  }
128
- async getErrorUrl(project, errorId, queryString = "") {
124
+ async getErrorUrl(project, errorId, queryString) {
129
125
  const dashboardUrl = await this.getDashboardUrl(project);
130
- return `${dashboardUrl}/errors/${errorId}${queryString}`;
126
+ return `${dashboardUrl}/errors/${errorId}${queryString ? `?${queryString}` : ""}`;
131
127
  }
132
128
  async getOrganization() {
133
129
  let org = this.cache.get(cacheKeys.ORG);
134
130
  if (!org) {
135
131
  const response = await this.currentUserApi.listUserOrganizations();
136
- const orgs = response.body || [];
132
+ const orgs = response.body;
137
133
  if (!orgs || orgs.length === 0) {
138
134
  throw new Error("No organizations found for the current user.");
139
135
  }
@@ -151,7 +147,7 @@ export class BugsnagClient {
151
147
  if (!projects) {
152
148
  const org = await this.getOrganization();
153
149
  const response = await this.currentUserApi.getOrganizationProjects(org.id);
154
- projects = response.body || [];
150
+ projects = response.body;
155
151
  this.cache.set(cacheKeys.PROJECTS, projects);
156
152
  }
157
153
  return projects;
@@ -164,9 +160,10 @@ export class BugsnagClient {
164
160
  let project = this.cache.get(cacheKeys.CURRENT_PROJECT) ?? null;
165
161
  if (!project && this.projectApiKey) {
166
162
  const projects = await this.getProjects();
167
- project = projects.find((p) => p.api_key === this.projectApiKey) ?? null;
163
+ project =
164
+ projects.find((p) => p.apiKey === this.projectApiKey) ?? null;
168
165
  if (!project) {
169
- 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.");
170
167
  }
171
168
  this.cache.set(cacheKeys.CURRENT_PROJECT, project);
172
169
  if (project) {
@@ -178,9 +175,9 @@ export class BugsnagClient {
178
175
  async getProjectEventFilters(project) {
179
176
  let filtersResponse = (await this.projectApi.listProjectEventFields(project.id)).body;
180
177
  if (!filtersResponse || filtersResponse.length === 0) {
181
- throw new Error(`No event fields found for project ${project.name}.`);
178
+ throw new ToolError(`No event fields found for project ${project.name}.`);
182
179
  }
183
- filtersResponse = filtersResponse.filter((field) => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
180
+ filtersResponse = filtersResponse.filter((field) => field.displayId && !EXCLUDED_EVENT_FIELDS.has(field.displayId));
184
181
  return filtersResponse;
185
182
  }
186
183
  async getEvent(eventId, projectId) {
@@ -190,108 +187,48 @@ export class BugsnagClient {
190
187
  const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch((_e) => null)));
191
188
  return projectEvents.find((event) => event && !!event.body)?.body || null;
192
189
  }
193
- async updateError(projectId, errorId, operation, options) {
194
- const errorUpdateRequest = {
195
- operation: operation,
196
- ...options,
197
- };
198
- const response = await this.errorsApi.updateErrorOnProject(projectId, errorId, errorUpdateRequest);
199
- return response.status === 200 || response.status === 204;
200
- }
201
190
  async getInputProject(projectId) {
202
191
  if (typeof projectId === "string") {
203
192
  const maybeProject = await this.getProject(projectId);
204
193
  if (!maybeProject) {
205
- throw new Error(`Project with ID ${projectId} not found.`);
194
+ throw new ToolError(`Project with ID ${projectId} not found.`);
206
195
  }
207
196
  return maybeProject;
208
197
  }
209
198
  else {
210
199
  const currentProject = await this.getCurrentProject();
211
200
  if (!currentProject) {
212
- 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.");
213
202
  }
214
203
  return currentProject;
215
204
  }
216
205
  }
217
- async listBuilds(projectId, opts) {
218
- const response = await this.projectApi.listBuilds(projectId, opts);
219
- const fetchedBuilds = response.body || [];
220
- const nextUrl = getNextUrlPathFromHeader(response.headers, this.apiEndpoint);
221
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
222
- const formattedBuilds = fetchedBuilds.map((b) => this.addStabilityData(b, stabilityTargets));
223
- return { builds: formattedBuilds, nextUrl };
224
- }
225
- async getBuild(projectId, buildId) {
226
- const cacheKey = `${cacheKeys.BUILD}_${buildId}`;
227
- const build = this.cache.get(cacheKey);
228
- if (build)
229
- return build;
230
- const fetchedBuild = (await this.projectApi.getBuild(projectId, buildId))
231
- .body;
232
- if (!fetchedBuild)
233
- throw new Error(`No build for ${buildId} found.`);
234
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
235
- const formattedBuild = this.addStabilityData(fetchedBuild, stabilityTargets);
236
- this.cache.set(cacheKey, formattedBuild, 5 * 60);
237
- return formattedBuild;
238
- }
239
- async listReleases(projectId, opts) {
240
- const response = await this.projectApi.listReleases(projectId, opts);
241
- const fetchedReleases = response.body || [];
242
- const nextUrl = getNextUrlPathFromHeader(response.headers, this.apiEndpoint);
243
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
244
- const formattedReleases = fetchedReleases.map((r) => this.addStabilityData(r, stabilityTargets));
245
- return { releases: formattedReleases, nextUrl };
246
- }
247
- async getRelease(projectId, releaseId) {
248
- const cacheKey = `${cacheKeys.RELEASE}_${releaseId}`;
249
- const release = this.cache.get(cacheKey);
250
- if (release)
251
- return release;
252
- const fetchedRelease = (await this.projectApi.getRelease(releaseId)).body;
253
- if (!fetchedRelease)
254
- throw new Error(`No release for ${releaseId} found.`);
255
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
256
- const formattedRelease = this.addStabilityData(fetchedRelease, stabilityTargets);
257
- this.cache.set(cacheKey, formattedRelease, 5 * 60);
258
- return formattedRelease;
259
- }
260
- async listBuildsInRelease(releaseId) {
261
- const cacheKey = `${cacheKeys.BUILDS_IN_RELEASE}_${releaseId}`;
262
- const builds = this.cache.get(cacheKey);
263
- if (builds)
264
- return builds;
265
- const fetchedBuilds = (await this.projectApi.listBuildsInRelease(releaseId)).body || [];
266
- this.cache.set(cacheKey, fetchedBuilds, 5 * 60);
267
- return fetchedBuilds;
268
- }
269
- async getProjectStabilityTargets(projectId) {
270
- return await this.projectApi.getProjectStabilityTargets(projectId);
271
- }
272
- addStabilityData(source, stabilityTargets) {
273
- const { stability_target_type, target_stability, critical_stability } = stabilityTargets;
274
- const user_stability = source.accumulative_daily_users_seen === 0 // avoid division by zero
206
+ addStabilityData(source, project) {
207
+ const accumulativeDailyUsersSeen = source.accumulativeDailyUsersSeen || 0;
208
+ const accumulativeDailyUsersWithUnhandled = source.accumulativeDailyUsersWithUnhandled || 0;
209
+ const userStability = accumulativeDailyUsersSeen === 0 // avoid division by zero
275
210
  ? 0
276
- : (source.accumulative_daily_users_seen -
277
- source.accumulative_daily_users_with_unhandled) /
278
- source.accumulative_daily_users_seen;
279
- 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
280
216
  ? 0
281
- : (source.total_sessions_count - source.unhandled_sessions_count) /
282
- source.total_sessions_count;
283
- const stabilityMetric = stability_target_type === "user" ? user_stability : session_stability;
284
- const meets_target_stability = stabilityMetric >= target_stability.value;
285
- const meets_critical_stability = stabilityMetric >= 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;
286
223
  return {
287
224
  ...source,
288
- user_stability,
289
- session_stability,
290
- stability_target_type,
291
- target_stability: target_stability.value,
292
- critical_stability: critical_stability.value,
293
- meets_target_stability,
294
- meets_critical_stability,
225
+ userStability,
226
+ sessionStability,
227
+ stabilityTargetType: project.stabilityTargetType || "user",
228
+ targetStability,
229
+ criticalStability,
230
+ meetsTargetStability,
231
+ meetsCriticalStability,
295
232
  };
296
233
  }
297
234
  registerTools(register, getInput) {
@@ -307,7 +244,7 @@ export class BugsnagClient {
307
244
  ],
308
245
  parameters: [
309
246
  {
310
- name: "page_size",
247
+ name: "pageSize",
311
248
  type: z.number(),
312
249
  description: "Number of projects to return per page for pagination",
313
250
  required: false,
@@ -325,7 +262,7 @@ export class BugsnagClient {
325
262
  {
326
263
  description: "Get first 10 projects",
327
264
  parameters: {
328
- page_size: 10,
265
+ pageSize: 10,
329
266
  page: 1,
330
267
  },
331
268
  expectedOutput: "JSON array of project objects with IDs, names, and metadata",
@@ -347,8 +284,8 @@ export class BugsnagClient {
347
284
  content: [{ type: "text", text: "No projects found." }],
348
285
  };
349
286
  }
350
- if (args.page_size || args.page) {
351
- const pageSize = args.page_size || 10;
287
+ if (args.pageSize || args.page) {
288
+ const pageSize = args.pageSize || 10;
352
289
  const page = args.page || 1;
353
290
  projects = projects.slice((page - 1) * pageSize, page * pageSize);
354
291
  }
@@ -430,30 +367,22 @@ export class BugsnagClient {
430
367
  }, async (args, _extra) => {
431
368
  const project = await this.getInputProject(args.projectId);
432
369
  if (!args.errorId)
433
- throw new Error("Both projectId and errorId arguments are required");
370
+ throw new ToolError("Both projectId and errorId arguments are required");
434
371
  const errorDetails = (await this.errorsApi.viewErrorOnProject(project.id, args.errorId)).body;
435
372
  if (!errorDetails) {
436
- 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}.`);
437
374
  }
438
- // Build query parameters
439
- const params = new URLSearchParams();
440
- // Add sorting and pagination parameters to get the latest event
441
- params.append("sort", "timestamp");
442
- params.append("direction", "desc");
443
- params.append("per_page", "1");
444
- params.append("full_reports", "true");
445
375
  const filters = {
446
376
  error: [{ type: "eq", value: args.errorId }],
447
377
  ...args.filters,
448
378
  };
449
- const filtersQueryString = toQueryString(filters);
450
- const listEventsQueryString = `?${params}&${filtersQueryString}`;
451
379
  // Get the latest event for this error using the events endpoint with filters
452
380
  let latestEvent = null;
453
381
  try {
454
- const eventsResponse = await this.errorsApi.listEventsOnProject(project.id, listEventsQueryString);
455
- const events = eventsResponse.body || [];
456
- latestEvent = events[0] || null;
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
+ }
457
386
  }
458
387
  catch (e) {
459
388
  console.warn("Failed to fetch latest event:", e);
@@ -462,9 +391,8 @@ export class BugsnagClient {
462
391
  const content = {
463
392
  error_details: errorDetails,
464
393
  latest_event: latestEvent,
465
- pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId))
466
- .body || [],
467
- 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()),
468
396
  };
469
397
  return {
470
398
  content: [{ type: "text", text: JSON.stringify(content) }],
@@ -508,17 +436,17 @@ export class BugsnagClient {
508
436
  ],
509
437
  }, async (args, _extra) => {
510
438
  if (!args.link)
511
- throw new Error("link argument is required");
439
+ throw new ToolError("link argument is required");
512
440
  const url = new URL(args.link);
513
441
  const eventId = url.searchParams.get("event_id");
514
442
  const projectSlug = url.pathname.split("/")[2];
515
443
  if (!projectSlug || !eventId)
516
- 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");
517
445
  // get the project id from list of projects
518
446
  const projects = await this.getProjects();
519
447
  const projectId = projects.find((p) => p.slug === projectSlug)?.id;
520
448
  if (!projectId) {
521
- throw new Error("Project with the specified slug not found.");
449
+ throw new ToolError("Project with the specified slug not found.");
522
450
  }
523
451
  const response = await this.getEvent(eventId, projectId);
524
452
  return {
@@ -573,15 +501,15 @@ export class BugsnagClient {
573
501
  examples: ["desc"],
574
502
  },
575
503
  {
576
- name: "per_page",
504
+ name: "perPage",
577
505
  type: z.number().min(1).max(100).default(30),
578
506
  description: "How many results to return per page.",
579
507
  required: false,
580
508
  examples: ["30", "50", "100"],
581
509
  },
582
510
  {
583
- name: "next",
584
- type: z.string().url(),
511
+ name: "nextUrl",
512
+ type: z.string(),
585
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.",
586
514
  required: false,
587
515
  examples: [
@@ -622,17 +550,17 @@ export class BugsnagClient {
622
550
  },
623
551
  sort: "users",
624
552
  direction: "desc",
625
- per_page: 10,
553
+ perPage: 10,
626
554
  },
627
555
  expectedOutput: "JSON object with a list of errors in the 'data' field, a count of the current page of results in the 'count' field, and a total count of all results in the 'total' field",
628
556
  },
629
557
  {
630
558
  description: "Get the next 50 results",
631
559
  parameters: {
632
- next: "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?base=2025-08-29T13%3A11%3A37Z&direction=desc&filters%5Berror.status%5D%5B%5D%5Btype%5D=eq&filters%5Berror.status%5D%5B%5D%5Bvalue%5D=open&offset=10&per_page=10&sort=users",
633
- per_page: 50,
560
+ nextUrl: "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?base=2025-08-29T13%3A11%3A37Z&direction=desc&filters%5Berror.status%5D%5B%5D%5Btype%5D=eq&filters%5Berror.status%5D%5B%5D%5Bvalue%5D=open&offset=10&per_page=10&sort=users",
561
+ perPage: 50,
634
562
  },
635
- expectedOutput: "JSON object with a list of errors in the 'data' field, a count of the current page of results in the 'count' field, and a total count of all results in the 'total' field",
563
+ expectedOutput: "JSON object with a list of errors, with a URL to the next page if more results are available and a total count of all errors matched",
636
564
  },
637
565
  ],
638
566
  hints: [
@@ -642,8 +570,8 @@ export class BugsnagClient {
642
570
  "Common time filters: event.since (from this time), event.before (until this time)",
643
571
  "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
644
572
  "There may not be any errors matching the filters - this is not a problem with the tool, in fact it might be a good thing that the user's application had no errors",
645
- "This tool returns paged results. The 'count' field indicates the number of results returned in the current page, and the 'total' field indicates the total number of results across all pages.",
646
- "If the output contains a 'next' value, there are more results available - call this tool again supplying the next URL as a parameter to retrieve the next page.",
573
+ "This tool returns paged results. The 'page_error_count' field indicates the number of results returned in the current page, and the 'total_error_count' field indicates the total number of results across all pages.",
574
+ "If the output contains a 'next_url' value, there are more results available - call this tool again supplying the next URL as a parameter to retrieve the next page.",
647
575
  "Do not modify the next URL as this can cause incorrect results. The only other parameter that can be used with 'next' is 'per_page' to control the page size.",
648
576
  ],
649
577
  }, async (args, _extra) => {
@@ -651,37 +579,24 @@ export class BugsnagClient {
651
579
  // Validate filter keys against cached event fields
652
580
  if (args.filters) {
653
581
  const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
654
- const validKeys = new Set(eventFields.map((f) => f.display_id));
582
+ const validKeys = new Set(eventFields.map((f) => f.displayId));
655
583
  for (const key of Object.keys(args.filters)) {
656
584
  if (!validKeys.has(key)) {
657
- throw new Error(`Invalid filter key: ${key}`);
585
+ throw new ToolError(`Invalid filter key: ${key}`);
658
586
  }
659
587
  }
660
588
  }
661
- const defaultFilters = {
589
+ const filters = {
662
590
  "event.since": [{ type: "eq", value: "30d" }],
663
591
  "error.status": [{ type: "eq", value: "open" }],
592
+ ...args.filters,
664
593
  };
665
- const options = {
666
- filters: { ...defaultFilters, ...args.filters },
667
- };
668
- if (args.sort !== undefined)
669
- options.sort = args.sort;
670
- if (args.direction !== undefined)
671
- options.direction = args.direction;
672
- if (args.per_page !== undefined)
673
- options.per_page = args.per_page;
674
- if (args.next !== undefined)
675
- options.next = args.next;
676
- const response = await this.errorsApi.listProjectErrors(project.id, options);
677
- const errors = response.body || [];
678
- const totalCount = response.headers.get("X-Total-Count");
679
- const linkHeader = response.headers.get("Link");
594
+ const response = await this.errorsApi.listProjectErrors(project.id, null, args.sort || "last_seen", args.direction || "desc", args.perPage || 30, filters, args.nextUrl);
680
595
  const result = {
681
- data: errors,
682
- count: errors.length,
683
- total: totalCount ? parseInt(totalCount, 10) : undefined,
684
- next: linkHeader?.match(/<([^>]+)>/)?.[1],
596
+ data: response.body,
597
+ next_url: response.nextUrl ?? undefined,
598
+ data_count: response.body?.length,
599
+ total_count: response.totalCount ?? undefined,
685
600
  };
686
601
  return {
687
602
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -711,7 +626,7 @@ export class BugsnagClient {
711
626
  }, async (_args, _extra) => {
712
627
  const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
713
628
  if (!projectFields)
714
- throw new Error("No event filters found in cache.");
629
+ throw new ToolError("No event filters found in cache.");
715
630
  return {
716
631
  content: [{ type: "text", text: JSON.stringify(projectFields) }],
717
632
  };
@@ -790,146 +705,24 @@ export class BugsnagClient {
790
705
  severity = result.content.severity;
791
706
  }
792
707
  }
793
- const result = await this.updateError(project.id, errorId, operation, {
794
- severity,
795
- });
796
- return {
797
- content: [
798
- { type: "text", text: JSON.stringify({ success: result }) },
799
- ],
800
- };
801
- });
802
- register({
803
- title: "List Builds",
804
- summary: "List builds for a project with optional filtering by release stage",
805
- purpose: "Retrieve a list of build summaries to analyze deployment history and associated errors",
806
- useCases: [
807
- "View recent builds to correlate with error spikes",
808
- "Filter builds by stage (e.g. production, staging) for targeted analysis",
809
- ],
810
- parameters: [
811
- ...(this.projectApiKey
812
- ? []
813
- : [
814
- {
815
- name: "projectId",
816
- type: z.string(),
817
- description: "ID of the project to list builds for",
818
- required: true,
819
- },
820
- ]),
821
- {
822
- name: "releaseStage",
823
- type: z.string(),
824
- description: "Filter builds by this stage (e.g. production, staging)",
825
- required: false,
826
- examples: ["production", "staging"],
827
- },
828
- {
829
- name: "nextUrl",
830
- type: z.string(),
831
- 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. If provided, other parameters are ignored.",
832
- required: false,
833
- examples: [
834
- "/projects/515fb9337c1074f6fd000003/builds?offset=30&per_page=30",
835
- ],
836
- },
837
- ],
838
- examples: [
839
- {
840
- description: "List all builds for a project",
841
- parameters: {},
842
- expectedOutput: "JSON array of build objects with metadata",
843
- },
844
- {
845
- description: "List production builds for a project",
846
- parameters: {
847
- releaseStage: "production",
848
- },
849
- expectedOutput: "JSON array of build objects in the production stage",
850
- },
851
- {
852
- description: "Get the next page of results",
853
- parameters: {
854
- nextUrl: "/projects/515fb9337c1074f6fd000003/builds?offset=30&per_page=30",
855
- },
856
- expectedOutput: "JSON array of build objects with metadata from the next page",
857
- },
858
- ],
859
- hints: ["For more detailed results use the Get Build tool"],
860
- readOnly: true,
861
- idempotent: true,
862
- outputFormat: "JSON array of build summary objects with metadata",
863
- }, async (args, _extra) => {
864
- const project = await this.getInputProject(args.projectId);
865
- const { builds, nextUrl } = await this.listBuilds(project.id, {
866
- release_stage: args.releaseStage,
867
- next_url: args.nextUrl,
708
+ const result = await this.errorsApi.updateErrorOnProject(project.id, errorId, {
709
+ operation: operation,
710
+ severity: severity,
868
711
  });
869
712
  return {
870
713
  content: [
871
714
  {
872
715
  type: "text",
873
716
  text: JSON.stringify({
874
- builds,
875
- next: nextUrl,
717
+ success: result.status === 200 || result.status === 204,
876
718
  }),
877
719
  },
878
720
  ],
879
721
  };
880
722
  });
881
- register({
882
- title: "Get Build",
883
- summary: "Get more details for a specific build by its ID",
884
- purpose: "Retrieve detailed information about a build for analysis and debugging",
885
- useCases: [
886
- "View build metadata such as version, source control info, and error counts",
887
- "Analyze a specific build to correlate with error spikes or deployments",
888
- "See the stability targets for a project and if the build meets them",
889
- ],
890
- parameters: [
891
- ...(this.projectApiKey
892
- ? []
893
- : [
894
- {
895
- name: "projectId",
896
- type: z.string(),
897
- description: "ID of the project containing the build",
898
- required: true,
899
- },
900
- ]),
901
- {
902
- name: "buildId",
903
- type: z.string(),
904
- description: "ID of the build to retrieve",
905
- required: true,
906
- examples: ["5f8d0d55c9e77c0017a1b2c3"],
907
- },
908
- ],
909
- examples: [
910
- {
911
- description: "Get details for a specific build",
912
- parameters: {
913
- buildId: "5f8d0d55c9e77c0017a1b2c3",
914
- },
915
- expectedOutput: "JSON object with build details including version, source control info, error counts and stability data.",
916
- },
917
- ],
918
- hints: ["Build IDs can be found using the List builds tool"],
919
- readOnly: true,
920
- idempotent: true,
921
- outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
922
- }, async (args, _extra) => {
923
- if (!args.buildId)
924
- throw new Error("buildId argument is required");
925
- const build = await this.getBuild((await this.getInputProject(args.projectId)).id, args.buildId);
926
- return {
927
- content: [{ type: "text", text: JSON.stringify(build) }],
928
- };
929
- });
930
723
  register({
931
724
  title: "List Releases",
932
- summary: "List releases for a project with optional filtering by release stage",
725
+ summary: "List releases for a project",
933
726
  purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
934
727
  useCases: [
935
728
  "View recent releases to correlate with error spikes",
@@ -948,18 +741,25 @@ export class BugsnagClient {
948
741
  ]),
949
742
  {
950
743
  name: "releaseStage",
951
- type: z.string(),
952
- description: "Filter releases by this stage (e.g. production, staging)",
744
+ type: z.string().default("production"),
745
+ description: "Filter releases by this stage (e.g. production, staging), defaults to 'production'",
953
746
  required: false,
954
747
  examples: ["production", "staging"],
955
748
  },
956
749
  {
957
750
  name: "visibleOnly",
958
- type: z.boolean().default(true),
959
- description: "Whether to only include releases that are marked as visible (default: true)",
960
- required: true,
751
+ type: z.boolean().default(false),
752
+ description: "Whether to only include releases that are marked as visible in the dashboard, defaults to false",
753
+ required: false,
961
754
  examples: ["true", "false"],
962
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
+ },
963
763
  {
964
764
  name: "nextUrl",
965
765
  type: z.string(),
@@ -972,16 +772,16 @@ export class BugsnagClient {
972
772
  ],
973
773
  examples: [
974
774
  {
975
- description: "List all releases for a project",
775
+ description: "List production releases for a project",
976
776
  parameters: {},
977
- expectedOutput: "JSON array of release objects with metadata",
777
+ expectedOutput: "JSON array of release objects in the production stage",
978
778
  },
979
779
  {
980
- description: "List production releases for a project",
780
+ description: "List staging releases for a project",
981
781
  parameters: {
982
- releaseStage: "production",
782
+ releaseStage: "staging",
983
783
  },
984
- expectedOutput: "JSON array of release objects in the production stage",
784
+ expectedOutput: "JSON array of release objects in the staging stage",
985
785
  },
986
786
  {
987
787
  description: "Get the next page of results",
@@ -991,23 +791,31 @@ export class BugsnagClient {
991
791
  expectedOutput: "JSON array of release objects with metadata from the next page",
992
792
  },
993
793
  ],
994
- hints: ["For more detailed results use the Get Release tool"],
794
+ hints: [
795
+ "Use the Get Release tool to get more details on a specific release, including the builds it contains",
796
+ "The release stage defaults to 'production' if not specified",
797
+ "Use visibleOnly to filter out releases that have been marked as hidden in the dashboard",
798
+ ],
995
799
  readOnly: true,
996
800
  idempotent: true,
997
- outputFormat: "JSON array of release summary objects with metadata",
801
+ outputFormat: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
998
802
  }, async (args, _extra) => {
999
- const { releases, nextUrl } = await this.listReleases((await this.getInputProject(args.projectId)).id, {
1000
- release_stage_name: args.releaseStage ?? "production",
1001
- visible_only: args.visibleOnly,
1002
- next_url: args.nextUrl ?? null,
1003
- });
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
+ }
1004
810
  return {
1005
811
  content: [
1006
812
  {
1007
813
  type: "text",
1008
814
  text: JSON.stringify({
1009
- releases,
1010
- next: nextUrl ?? null,
815
+ data: releases,
816
+ next_url: response.nextUrl ?? undefined,
817
+ data_count: releases.length,
818
+ total_count: response.totalCount ?? undefined,
1011
819
  }),
1012
820
  },
1013
821
  ],
@@ -1015,12 +823,12 @@ export class BugsnagClient {
1015
823
  });
1016
824
  register({
1017
825
  title: "Get Release",
1018
- summary: "Get more details for a specific release by its ID",
826
+ summary: "Get more details for a specific release by its ID, including source control information and associated builds",
1019
827
  purpose: "Retrieve detailed information about a release for analysis and debugging",
1020
828
  useCases: [
1021
829
  "View release metadata such as version, source control info, and error counts",
1022
- "Analyze a specific release to correlate with error spikes or deployments",
1023
- "See the stability targets for a project and if the release meets them",
830
+ "Analyze the stability data and targets for a release",
831
+ "See the builds that make up the release",
1024
832
  ],
1025
833
  parameters: [
1026
834
  ...(this.projectApiKey
@@ -1056,19 +864,39 @@ export class BugsnagClient {
1056
864
  outputFormat: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
1057
865
  }, async (args, _extra) => {
1058
866
  if (!args.releaseId)
1059
- throw new Error("releaseId argument is required");
1060
- const release = await this.getRelease((await this.getInputProject(args.projectId)).id, args.releaseId);
867
+ throw new ToolError("releaseId argument is required");
868
+ const project = await this.getInputProject(args.projectId);
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
+ }
1061
880
  return {
1062
- content: [{ type: "text", text: JSON.stringify(release) }],
881
+ content: [
882
+ {
883
+ type: "text",
884
+ text: JSON.stringify({
885
+ release: release,
886
+ builds: builds,
887
+ }),
888
+ },
889
+ ],
1063
890
  };
1064
891
  });
1065
892
  register({
1066
- title: "List Builds in Release",
1067
- summary: "List builds associated with a specific release",
1068
- purpose: "Retrieve a list of builds for a given release to analyze deployment history and associated errors",
893
+ title: "Get Build",
894
+ summary: "Get more details for a specific build by its ID",
895
+ purpose: "Retrieve detailed information about a build for analysis and debugging",
1069
896
  useCases: [
1070
- "View builds within a release to correlate with error spikes",
1071
- "Analyze the composition of a release by examining its builds",
897
+ "View build metadata such as version, source control info, and error counts",
898
+ "Analyze a specific build to correlate with error spikes or deployments",
899
+ "See the stability targets for a project and if the build meets them",
1072
900
  ],
1073
901
  parameters: [
1074
902
  ...(this.projectApiKey
@@ -1077,37 +905,41 @@ export class BugsnagClient {
1077
905
  {
1078
906
  name: "projectId",
1079
907
  type: z.string(),
1080
- description: "ID of the project containing the release",
908
+ description: "ID of the project containing the build",
1081
909
  required: true,
1082
910
  },
1083
911
  ]),
1084
912
  {
1085
- name: "releaseId",
913
+ name: "buildId",
1086
914
  type: z.string(),
1087
- description: "ID of the release to list builds for",
915
+ description: "ID of the build to retrieve",
1088
916
  required: true,
1089
917
  examples: ["5f8d0d55c9e77c0017a1b2c3"],
1090
918
  },
1091
919
  ],
1092
920
  examples: [
1093
921
  {
1094
- description: "List all builds in a specific release",
922
+ description: "Get details for a specific build",
1095
923
  parameters: {
1096
- releaseId: "5f8d0d55c9e77c0017a1b2c3",
924
+ buildId: "5f8d0d55c9e77c0017a1b2c3",
1097
925
  },
1098
- expectedOutput: "JSON array of build objects with metadata",
926
+ expectedOutput: "JSON object with build details including version, source control info, error counts and stability data.",
1099
927
  },
1100
928
  ],
1101
- hints: ["Release IDs can be found using the List releases tool"],
929
+ hints: ["Build IDs can be found using the List builds tool"],
1102
930
  readOnly: true,
1103
931
  idempotent: true,
1104
- outputFormat: "JSON array of build summary objects with metadata",
932
+ outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
1105
933
  }, async (args, _extra) => {
1106
- if (!args.releaseId)
1107
- throw new Error("releaseId argument is required");
1108
- const builds = await this.listBuildsInRelease(args.releaseId);
934
+ if (!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);
1109
941
  return {
1110
- content: [{ type: "text", text: JSON.stringify(builds) }],
942
+ content: [{ type: "text", text: JSON.stringify(build) }],
1111
943
  };
1112
944
  });
1113
945
  }