@smartbear/mcp 0.6.0 → 0.8.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 (41) hide show
  1. package/README.md +37 -3
  2. package/dist/api-hub/client/api.js +387 -0
  3. package/dist/api-hub/client/configuration.js +27 -0
  4. package/dist/api-hub/client/index.js +5 -0
  5. package/dist/api-hub/client/portal-types.js +131 -0
  6. package/dist/api-hub/client/registry-types.js +69 -0
  7. package/dist/api-hub/client/tools.js +98 -0
  8. package/dist/api-hub/client.js +70 -404
  9. package/dist/bugsnag/client/api/CurrentUser.js +19 -13
  10. package/dist/bugsnag/client/api/Error.js +45 -57
  11. package/dist/bugsnag/client/api/Project.js +35 -30
  12. package/dist/bugsnag/client/api/base.js +24 -9
  13. package/dist/bugsnag/client/api/filters.js +9 -9
  14. package/dist/bugsnag/client.js +281 -373
  15. package/dist/common/info.js +1 -1
  16. package/dist/common/server.js +39 -28
  17. package/dist/index.js +18 -4
  18. package/dist/pactflow/client/ai.js +20 -20
  19. package/dist/pactflow/client/base.js +48 -13
  20. package/dist/pactflow/client/prompts.js +10 -12
  21. package/dist/pactflow/client/tools.js +18 -18
  22. package/dist/pactflow/client/utils.js +1 -1
  23. package/dist/pactflow/client.js +23 -15
  24. package/dist/qmetry/client/api/client-api.js +39 -0
  25. package/dist/qmetry/client/handlers.js +11 -0
  26. package/dist/qmetry/client/project.js +27 -0
  27. package/dist/qmetry/client/testcase.js +104 -0
  28. package/dist/qmetry/client/tools.js +222 -0
  29. package/dist/qmetry/client.js +95 -0
  30. package/dist/qmetry/config/constants.js +12 -0
  31. package/dist/qmetry/config/rest-endpoints.js +11 -0
  32. package/dist/qmetry/types/common.js +174 -0
  33. package/dist/qmetry/types/testcase.js +19 -0
  34. package/dist/reflect/client.js +14 -14
  35. package/dist/zephyr/client.js +16 -0
  36. package/dist/zephyr/common/api-client.js +27 -0
  37. package/dist/zephyr/common/auth-service.js +14 -0
  38. package/dist/zephyr/common/types.js +35 -0
  39. package/dist/zephyr/tool/project/get-projects.js +54 -0
  40. package/dist/zephyr/tool/zephyr-tool.js +1 -0
  41. package/package.json +8 -6
@@ -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 { CurrentUserAPI, ErrorAPI, Configuration } from "./client/index.js";
5
- import { FilterObjectSchema, toQueryString } from "./client/api/filters.js";
4
+ import { FilterObjectSchema, toQueryString, } from "./client/api/filters.js";
6
5
  import { ProjectAPI, } from "./client/api/Project.js";
7
- import { getNextUrlPathFromHeader } from "./client/api/base.js";
6
+ import { Configuration, CurrentUserAPI, ErrorAPI } from "./client/index.js";
8
7
  const HUB_PREFIX = "00000";
9
8
  const DEFAULT_DOMAIN = "bugsnag.com";
10
9
  const HUB_DOMAIN = "bugsnag.smartbear.com";
@@ -13,13 +12,10 @@ 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([
22
- "search" // This is searches multiple fields and is more a convenience for humans, we're removing to avoid over-matching
18
+ "search", // This is searches multiple fields and is more a convenience for humans, we're removing to avoid over-matching
23
19
  ]);
24
20
  const PERMITTED_UPDATE_OPERATIONS = [
25
21
  "override_severity",
@@ -27,7 +23,7 @@ const PERMITTED_UPDATE_OPERATIONS = [
27
23
  "fix",
28
24
  "ignore",
29
25
  "discard",
30
- "undiscard"
26
+ "undiscard",
31
27
  ];
32
28
  export class BugsnagClient {
33
29
  currentUserApi;
@@ -82,7 +78,7 @@ export class BugsnagClient {
82
78
  }
83
79
  }
84
80
  getHost(apiKey, subdomain) {
85
- if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
81
+ if (apiKey?.startsWith(HUB_PREFIX)) {
86
82
  return `https://${subdomain}.${HUB_DOMAIN}`;
87
83
  }
88
84
  else {
@@ -95,7 +91,7 @@ export class BugsnagClient {
95
91
  getEndpoint(subdomain, apiKey, endpoint) {
96
92
  let subDomainEndpoint;
97
93
  if (!endpoint) {
98
- if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
94
+ if (apiKey?.startsWith(HUB_PREFIX)) {
99
95
  subDomainEndpoint = `https://${subdomain}.${HUB_DOMAIN}`;
100
96
  }
101
97
  else {
@@ -105,7 +101,8 @@ export class BugsnagClient {
105
101
  else {
106
102
  // check if the endpoint matches either the HUB_DOMAIN or DEFAULT_DOMAIN
107
103
  const url = new URL(endpoint);
108
- if (url.hostname.endsWith(HUB_DOMAIN) || url.hostname.endsWith(DEFAULT_DOMAIN)) {
104
+ if (url.hostname.endsWith(HUB_DOMAIN) ||
105
+ url.hostname.endsWith(DEFAULT_DOMAIN)) {
109
106
  // For known domains (Hub or Bugsnag), always use HTTPS and standard format
110
107
  if (url.hostname.endsWith(HUB_DOMAIN)) {
111
108
  subDomainEndpoint = `https://${subdomain}.${HUB_DOMAIN}`;
@@ -124,7 +121,7 @@ export class BugsnagClient {
124
121
  async getDashboardUrl(project) {
125
122
  return `${this.appEndpoint}/${(await this.getOrganization()).slug}/${project.slug}`;
126
123
  }
127
- async getErrorUrl(project, errorId, queryString = '') {
124
+ async getErrorUrl(project, errorId, queryString = "") {
128
125
  const dashboardUrl = await this.getDashboardUrl(project);
129
126
  return `${dashboardUrl}/errors/${errorId}${queryString}`;
130
127
  }
@@ -165,7 +162,7 @@ export class BugsnagClient {
165
162
  const projects = await this.getProjects();
166
163
  project = projects.find((p) => p.api_key === this.projectApiKey) ?? null;
167
164
  if (!project) {
168
- throw new Error(`Unable to find project with API key ${this.projectApiKey} in organization.`);
165
+ throw new Error("Unable to find project with the configured API key.");
169
166
  }
170
167
  this.cache.set(cacheKeys.CURRENT_PROJECT, project);
171
168
  if (project) {
@@ -179,24 +176,26 @@ export class BugsnagClient {
179
176
  if (!filtersResponse || filtersResponse.length === 0) {
180
177
  throw new Error(`No event fields found for project ${project.name}.`);
181
178
  }
182
- filtersResponse = filtersResponse.filter(field => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
179
+ filtersResponse = filtersResponse.filter((field) => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
183
180
  return filtersResponse;
184
181
  }
185
182
  async getEvent(eventId, projectId) {
186
- const projectIds = projectId ? [projectId] : (await this.getProjects()).map((p) => p.id);
187
- const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch(_e => null)));
188
- return projectEvents.find(event => event && !!event.body)?.body || null;
183
+ const projectIds = projectId
184
+ ? [projectId]
185
+ : (await this.getProjects()).map((p) => p.id);
186
+ const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch((_e) => null)));
187
+ return projectEvents.find((event) => event && !!event.body)?.body || null;
189
188
  }
190
189
  async updateError(projectId, errorId, operation, options) {
191
190
  const errorUpdateRequest = {
192
191
  operation: operation,
193
- ...options
192
+ ...options,
194
193
  };
195
194
  const response = await this.errorsApi.updateErrorOnProject(projectId, errorId, errorUpdateRequest);
196
195
  return response.status === 200 || response.status === 204;
197
196
  }
198
197
  async getInputProject(projectId) {
199
- if (typeof projectId === 'string') {
198
+ if (typeof projectId === "string") {
200
199
  const maybeProject = await this.getProject(projectId);
201
200
  if (!maybeProject) {
202
201
  throw new Error(`Project with ID ${projectId} not found.`);
@@ -206,84 +205,90 @@ export class BugsnagClient {
206
205
  else {
207
206
  const currentProject = await this.getCurrentProject();
208
207
  if (!currentProject) {
209
- throw new Error('No current project found. Please provide a projectId or configure a project API key.');
208
+ throw new Error("No current project found. Please provide a projectId or configure a project API key.");
210
209
  }
211
210
  return currentProject;
212
211
  }
213
212
  }
214
213
  async listBuilds(projectId, opts) {
215
214
  const response = await this.projectApi.listBuilds(projectId, opts);
216
- const fetchedBuilds = response.body || [];
217
- const nextUrl = getNextUrlPathFromHeader(response.headers, this.apiEndpoint);
218
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
219
- const formattedBuilds = fetchedBuilds.map((b) => this.addStabilityData(b, stabilityTargets));
220
- return { builds: formattedBuilds, nextUrl };
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
+ };
221
225
  }
222
226
  async getBuild(projectId, buildId) {
223
- const cacheKey = `${cacheKeys.BUILD}_${buildId}`;
224
- const build = this.cache.get(cacheKey);
225
- if (build)
226
- return build;
227
- const fetchedBuild = (await this.projectApi.getBuild(projectId, buildId)).body;
228
- if (!fetchedBuild)
227
+ const response = await this.projectApi.getBuild(projectId, buildId);
228
+ if (!response.body)
229
229
  throw new Error(`No build for ${buildId} found.`);
230
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
231
- const formattedBuild = this.addStabilityData(fetchedBuild, stabilityTargets);
232
- this.cache.set(cacheKey, formattedBuild, 5 * 60);
233
- return formattedBuild;
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
234
  }
235
235
  async listReleases(projectId, opts) {
236
236
  const response = await this.projectApi.listReleases(projectId, opts);
237
- const fetchedReleases = response.body || [];
238
- const nextUrl = getNextUrlPathFromHeader(response.headers, this.apiEndpoint);
239
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
240
- const formattedReleases = fetchedReleases.map((r) => this.addStabilityData(r, stabilityTargets));
241
- return { releases: formattedReleases, nextUrl };
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
+ };
242
247
  }
243
248
  async getRelease(projectId, releaseId) {
244
- const cacheKey = `${cacheKeys.RELEASE}_${releaseId}`;
245
- const release = this.cache.get(cacheKey);
246
- if (release)
247
- return release;
248
- const fetchedRelease = (await this.projectApi.getRelease(releaseId)).body;
249
- if (!fetchedRelease)
249
+ const response = await this.projectApi.getRelease(releaseId);
250
+ if (!response.body)
250
251
  throw new Error(`No release for ${releaseId} found.`);
251
- const stabilityTargets = await this.getProjectStabilityTargets(projectId);
252
- const formattedRelease = this.addStabilityData(fetchedRelease, stabilityTargets);
253
- this.cache.set(cacheKey, formattedRelease, 5 * 60);
254
- return formattedRelease;
255
- }
256
- async listBuildsInRelease(releaseId) {
257
- const cacheKey = `${cacheKeys.BUILDS_IN_RELEASE}_${releaseId}`;
258
- const builds = this.cache.get(cacheKey);
259
- if (builds)
260
- return builds;
261
- const fetchedBuilds = (await this.projectApi.listBuildsInRelease(releaseId)).body || [];
262
- this.cache.set(cacheKey, fetchedBuilds, 5 * 60);
263
- return fetchedBuilds;
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) };
264
256
  }
265
- async getProjectStabilityTargets(projectId) {
266
- return await this.projectApi.getProjectStabilityTargets(projectId);
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
+ };
267
269
  }
268
- addStabilityData(source, stabilityTargets) {
269
- const { stability_target_type, target_stability, critical_stability } = stabilityTargets;
270
+ addStabilityData(source, project) {
270
271
  const user_stability = source.accumulative_daily_users_seen === 0 // avoid division by zero
271
272
  ? 0
272
- : (source.accumulative_daily_users_seen - source.accumulative_daily_users_with_unhandled) /
273
+ : (source.accumulative_daily_users_seen -
274
+ source.accumulative_daily_users_with_unhandled) /
273
275
  source.accumulative_daily_users_seen;
274
276
  const session_stability = source.total_sessions_count === 0 // avoid division by zero
275
277
  ? 0
276
- : (source.total_sessions_count - source.unhandled_sessions_count) / source.total_sessions_count;
277
- const stabilityMetric = stability_target_type === "user" ? user_stability : session_stability;
278
- const meets_target_stability = stabilityMetric >= target_stability.value;
279
- const meets_critical_stability = stabilityMetric >= critical_stability.value;
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;
280
285
  return {
281
286
  ...source,
282
287
  user_stability,
283
288
  session_stability,
284
- stability_target_type,
285
- target_stability: target_stability.value,
286
- critical_stability: critical_stability.value,
289
+ stability_target_type: project.stability_target_type,
290
+ target_stability: project.target_stability.value,
291
+ critical_stability: project.critical_stability.value,
287
292
  meets_target_stability,
288
293
  meets_critical_stability,
289
294
  };
@@ -297,43 +302,43 @@ export class BugsnagClient {
297
302
  useCases: [
298
303
  "Browse available projects when no specific project API key is configured",
299
304
  "Find project IDs needed for other tools",
300
- "Get an overview of all projects in the organization"
305
+ "Get an overview of all projects in the organization",
301
306
  ],
302
307
  parameters: [
303
308
  {
304
- name: "page_size",
309
+ name: "pageSize",
305
310
  type: z.number(),
306
311
  description: "Number of projects to return per page for pagination",
307
312
  required: false,
308
- examples: ["10", "25", "50"]
313
+ examples: ["10", "25", "50"],
309
314
  },
310
315
  {
311
316
  name: "page",
312
317
  type: z.number(),
313
318
  description: "Page number to return (starts from 1)",
314
319
  required: false,
315
- examples: ["1", "2", "3"]
316
- }
320
+ examples: ["1", "2", "3"],
321
+ },
317
322
  ],
318
323
  examples: [
319
324
  {
320
325
  description: "Get first 10 projects",
321
326
  parameters: {
322
- page_size: 10,
323
- page: 1
327
+ pageSize: 10,
328
+ page: 1,
324
329
  },
325
330
  expectedOutput: "JSON array of project objects with IDs, names, and metadata",
326
331
  },
327
332
  {
328
333
  description: "Get all projects (no pagination)",
329
334
  parameters: {},
330
- expectedOutput: "JSON array of all available projects"
331
- }
335
+ expectedOutput: "JSON array of all available projects",
336
+ },
332
337
  ],
333
338
  hints: [
334
339
  "Use pagination for organizations with many projects to avoid large responses",
335
- "Project IDs from this list can be used with other tools when no project API key is configured"
336
- ]
340
+ "Project IDs from this list can be used with other tools when no project API key is configured",
341
+ ],
337
342
  }, async (args, _extra) => {
338
343
  let projects = await this.getProjects();
339
344
  if (!projects || projects.length === 0) {
@@ -341,8 +346,8 @@ export class BugsnagClient {
341
346
  content: [{ type: "text", text: "No projects found." }],
342
347
  };
343
348
  }
344
- if (args.page_size || args.page) {
345
- const pageSize = args.page_size || 10;
349
+ if (args.pageSize || args.page) {
350
+ const pageSize = args.pageSize || 10;
346
351
  const page = args.page || 1;
347
352
  projects = projects.slice((page - 1) * pageSize, page * pageSize);
348
353
  }
@@ -363,7 +368,7 @@ export class BugsnagClient {
363
368
  "Investigate a specific error found through the List Project Errors tool",
364
369
  "Understand which types of user are affected by the error using summarized event data",
365
370
  "Get error details for debugging and root cause analysis",
366
- "Retrieve error metadata for incident reports and documentation"
371
+ "Retrieve error metadata for incident reports and documentation",
367
372
  ],
368
373
  parameters: [
369
374
  {
@@ -371,16 +376,18 @@ export class BugsnagClient {
371
376
  type: z.string(),
372
377
  required: true,
373
378
  description: "Unique identifier of the error to retrieve",
374
- examples: ["6863e2af8c857c0a5023b411"]
379
+ examples: ["6863e2af8c857c0a5023b411"],
375
380
  },
376
- ...(this.projectApiKey ? [] : [
377
- {
378
- name: "projectId",
379
- type: z.string(),
380
- required: true,
381
- description: "ID of the project containing the error",
382
- }
383
- ]),
381
+ ...(this.projectApiKey
382
+ ? []
383
+ : [
384
+ {
385
+ name: "projectId",
386
+ type: z.string(),
387
+ required: true,
388
+ description: "ID of the project containing the error",
389
+ },
390
+ ]),
384
391
  {
385
392
  name: "filters",
386
393
  type: FilterObjectSchema,
@@ -390,14 +397,14 @@ export class BugsnagClient {
390
397
  '{"error.status": [{"type": "eq", "value": "open"}]}',
391
398
  '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
392
399
  '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
393
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}'
400
+ '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
394
401
  ],
395
402
  constraints: [
396
403
  "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
397
404
  "ISO 8601 times must be in UTC and use extended format",
398
- "Relative time periods: h (hours), d (days)"
399
- ]
400
- }
405
+ "Relative time periods: h (hours), d (days)",
406
+ ],
407
+ },
401
408
  ],
402
409
  outputFormat: "JSON object containing: " +
403
410
  " - error_details: Aggregated data about the error, including first and last seen occurrence" +
@@ -408,10 +415,10 @@ export class BugsnagClient {
408
415
  {
409
416
  description: "Get details for a specific error",
410
417
  parameters: {
411
- errorId: "6863e2af8c857c0a5023b411"
418
+ errorId: "6863e2af8c857c0a5023b411",
412
419
  },
413
- expectedOutput: "JSON object with error details including message, stack trace, occurrence count, and metadata"
414
- }
420
+ expectedOutput: "JSON object with error details including message, stack trace, occurrence count, and metadata",
421
+ },
415
422
  ],
416
423
  hints: [
417
424
  "Error IDs can be found using the List Project Errors tool",
@@ -430,22 +437,20 @@ export class BugsnagClient {
430
437
  // Build query parameters
431
438
  const params = new URLSearchParams();
432
439
  // Add sorting and pagination parameters to get the latest event
433
- params.append('sort', 'timestamp');
434
- params.append('direction', 'desc');
435
- params.append('per_page', '1');
436
- params.append('full_reports', 'true');
440
+ params.append("sort", "timestamp");
441
+ params.append("direction", "desc");
442
+ params.append("per_page", "1");
443
+ params.append("full_reports", "true");
437
444
  const filters = {
438
- "error": [{ type: "eq", value: args.errorId }],
439
- ...args.filters
445
+ error: [{ type: "eq", value: args.errorId }],
446
+ ...args.filters,
440
447
  };
441
448
  const filtersQueryString = toQueryString(filters);
442
449
  const listEventsQueryString = `?${params}&${filtersQueryString}`;
443
450
  // Get the latest event for this error using the events endpoint with filters
444
451
  let latestEvent = null;
445
452
  try {
446
- const eventsResponse = await this.errorsApi.listEventsOnProject(project.id, listEventsQueryString);
447
- const events = eventsResponse.body || [];
448
- latestEvent = events[0] || null;
453
+ latestEvent = (await this.errorsApi.getLatestEventOnProject(project.id, listEventsQueryString)).body;
449
454
  }
450
455
  catch (e) {
451
456
  console.warn("Failed to fetch latest event:", e);
@@ -454,11 +459,12 @@ export class BugsnagClient {
454
459
  const content = {
455
460
  error_details: errorDetails,
456
461
  latest_event: latestEvent,
457
- pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId)).body || [],
462
+ pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId))
463
+ .body || [],
458
464
  url: await this.getErrorUrl(project, args.errorId, `?${filtersQueryString}`),
459
465
  };
460
466
  return {
461
- content: [{ type: "text", text: JSON.stringify(content) }]
467
+ content: [{ type: "text", text: JSON.stringify(content) }],
462
468
  };
463
469
  });
464
470
  register({
@@ -468,7 +474,7 @@ export class BugsnagClient {
468
474
  useCases: [
469
475
  "Get event details when given a dashboard URL from a user or notification",
470
476
  "Extract event information from shared links or browser URLs",
471
- "Quick lookup of event details without needing separate project and event IDs"
477
+ "Quick lookup of event details without needing separate project and event IDs",
472
478
  ],
473
479
  parameters: [
474
480
  {
@@ -477,32 +483,32 @@ export class BugsnagClient {
477
483
  description: "Full URL to the event details page in the BugSnag dashboard (web interface)",
478
484
  required: true,
479
485
  examples: [
480
- "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
486
+ "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000",
481
487
  ],
482
488
  constraints: [
483
- "Must be a valid dashboard URL containing project slug and event_id parameter"
484
- ]
485
- }
489
+ "Must be a valid dashboard URL containing project slug and event_id parameter",
490
+ ],
491
+ },
486
492
  ],
487
493
  examples: [
488
494
  {
489
495
  description: "Get event details from a dashboard URL",
490
496
  parameters: {
491
- link: "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
497
+ link: "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000",
492
498
  },
493
- expectedOutput: "JSON object with complete event details including stack trace, metadata, and context"
494
- }
499
+ expectedOutput: "JSON object with complete event details including stack trace, metadata, and context",
500
+ },
495
501
  ],
496
502
  hints: [
497
503
  "The URL must contain both project slug in the path and event_id in query parameters",
498
- "This is useful when users share BugSnag dashboard URLs and you need to extract the event data"
499
- ]
504
+ "This is useful when users share BugSnag dashboard URLs and you need to extract the event data",
505
+ ],
500
506
  }, async (args, _extra) => {
501
507
  if (!args.link)
502
508
  throw new Error("link argument is required");
503
509
  const url = new URL(args.link);
504
510
  const eventId = url.searchParams.get("event_id");
505
- const projectSlug = url.pathname.split('/')[2];
511
+ const projectSlug = url.pathname.split("/")[2];
506
512
  if (!projectSlug || !eventId)
507
513
  throw new Error("Both projectSlug and eventId must be present in the link");
508
514
  // get the project id from list of projects
@@ -524,14 +530,14 @@ export class BugsnagClient {
524
530
  "Debug recent application errors by filtering for open errors in the last 7 days",
525
531
  "Generate error reports for stakeholders by filtering specific error types or severity levels",
526
532
  "Monitor error trends over time using date range filters",
527
- "Find errors affecting specific users or environments using metadata filters"
533
+ "Find errors affecting specific users or environments using metadata filters",
528
534
  ],
529
535
  parameters: [
530
536
  {
531
537
  name: "filters",
532
538
  type: FilterObjectSchema.default({
533
539
  "event.since": [{ type: "eq", value: "30d" }],
534
- "error.status": [{ type: "eq", value: "open" }]
540
+ "error.status": [{ type: "eq", value: "open" }],
535
541
  }),
536
542
  description: "Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields",
537
543
  required: false,
@@ -539,84 +545,92 @@ export class BugsnagClient {
539
545
  '{"error.status": [{"type": "eq", "value": "open"}]}',
540
546
  '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
541
547
  '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
542
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}'
548
+ '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
543
549
  ],
544
550
  constraints: [
545
551
  "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
546
552
  "ISO 8601 times must be in UTC and use extended format",
547
- "Relative time periods: h (hours), d (days)"
548
- ]
553
+ "Relative time periods: h (hours), d (days)",
554
+ ],
549
555
  },
550
556
  {
551
557
  name: "sort",
552
- type: z.enum(["first_seen", "last_seen", "events", "users", "unsorted"]).default("last_seen"),
558
+ type: z
559
+ .enum(["first_seen", "last_seen", "events", "users", "unsorted"])
560
+ .default("last_seen"),
553
561
  description: "Field to sort the errors by",
554
562
  required: false,
555
- examples: ["last_seen"]
563
+ examples: ["last_seen"],
556
564
  },
557
565
  {
558
566
  name: "direction",
559
567
  type: z.enum(["asc", "desc"]).default("desc"),
560
568
  description: "Sort direction for ordering results",
561
569
  required: false,
562
- examples: ["desc"]
570
+ examples: ["desc"],
563
571
  },
564
572
  {
565
- name: "per_page",
573
+ name: "perPage",
566
574
  type: z.number().min(1).max(100).default(30),
567
575
  description: "How many results to return per page.",
568
576
  required: false,
569
- examples: ["30", "50", "100"]
577
+ examples: ["30", "50", "100"],
570
578
  },
571
579
  {
572
- name: "next",
580
+ name: "nextUrl",
573
581
  type: z.string().url(),
574
582
  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.",
575
583
  required: false,
576
- examples: ["https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?offset=30&per_page=30&sort=last_seen"],
577
- constraints: ["Only values provided in the output from this tool can be used. Do not attempt to construct it manually."]
584
+ examples: [
585
+ "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?offset=30&per_page=30&sort=last_seen",
586
+ ],
587
+ constraints: [
588
+ "Only values provided in the output from this tool can be used. Do not attempt to construct it manually.",
589
+ ],
578
590
  },
579
- ...(this.projectApiKey ? [] : [
580
- {
581
- name: "projectId",
582
- type: z.string(),
583
- description: "ID of the project to query for errors",
584
- required: true,
585
- }
586
- ])
591
+ ...(this.projectApiKey
592
+ ? []
593
+ : [
594
+ {
595
+ name: "projectId",
596
+ type: z.string(),
597
+ description: "ID of the project to query for errors",
598
+ required: true,
599
+ },
600
+ ]),
587
601
  ],
588
602
  examples: [
589
603
  {
590
604
  description: "Find errors affecting a specific user in the last 24 hours",
591
605
  parameters: {
592
606
  filters: {
593
- "user.email": [{ "type": "eq", "value": "user@example.com" }],
594
- "event.since": [{ "type": "eq", "value": "24h" }]
595
- }
607
+ "user.email": [{ type: "eq", value: "user@example.com" }],
608
+ "event.since": [{ type: "eq", value: "24h" }],
609
+ },
596
610
  },
597
- 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"
611
+ 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",
598
612
  },
599
613
  {
600
614
  description: "Get the 10 open errors with the most users affected in the last 30 days",
601
615
  parameters: {
602
616
  filters: {
603
- "event.since": [{ "type": "eq", "value": "30d" }],
604
- "error.status": [{ "type": "eq", "value": "open" }]
617
+ "event.since": [{ type: "eq", value: "30d" }],
618
+ "error.status": [{ type: "eq", value: "open" }],
605
619
  },
606
620
  sort: "users",
607
621
  direction: "desc",
608
- per_page: 10
622
+ perPage: 10,
609
623
  },
610
- 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"
624
+ 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",
611
625
  },
612
626
  {
613
627
  description: "Get the next 50 results",
614
628
  parameters: {
615
- 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",
616
- per_page: 50
629
+ 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",
630
+ perPage: 50,
617
631
  },
618
- 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"
619
- }
632
+ 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",
633
+ },
620
634
  ],
621
635
  hints: [
622
636
  "Use list_project_event_filters tool first to discover valid filter field names for your project",
@@ -625,16 +639,16 @@ export class BugsnagClient {
625
639
  "Common time filters: event.since (from this time), event.before (until this time)",
626
640
  "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
627
641
  "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",
628
- "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.",
629
- "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.",
630
- "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."
631
- ]
642
+ "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.",
643
+ "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.",
644
+ "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.",
645
+ ],
632
646
  }, async (args, _extra) => {
633
647
  const project = await this.getInputProject(args.projectId);
634
648
  // Validate filter keys against cached event fields
635
649
  if (args.filters) {
636
650
  const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
637
- const validKeys = new Set(eventFields.map(f => f.display_id));
651
+ const validKeys = new Set(eventFields.map((f) => f.display_id));
638
652
  for (const key of Object.keys(args.filters)) {
639
653
  if (!validKeys.has(key)) {
640
654
  throw new Error(`Invalid filter key: ${key}`);
@@ -642,29 +656,26 @@ export class BugsnagClient {
642
656
  }
643
657
  }
644
658
  const defaultFilters = {
645
- "event.since": [{ "type": "eq", "value": "30d" }],
646
- "error.status": [{ "type": "eq", "value": "open" }]
659
+ "event.since": [{ type: "eq", value: "30d" }],
660
+ "error.status": [{ type: "eq", value: "open" }],
647
661
  };
648
662
  const options = {
649
- filters: { ...defaultFilters, ...args.filters }
663
+ filters: { ...defaultFilters, ...args.filters },
650
664
  };
651
665
  if (args.sort !== undefined)
652
666
  options.sort = args.sort;
653
667
  if (args.direction !== undefined)
654
668
  options.direction = args.direction;
655
- if (args.per_page !== undefined)
656
- options.per_page = args.per_page;
657
- if (args.next !== undefined)
658
- options.next = args.next;
669
+ if (args.perPage !== undefined)
670
+ options.per_page = args.perPage;
671
+ if (args.nextUrl !== undefined)
672
+ options.next_url = args.nextUrl;
659
673
  const response = await this.errorsApi.listProjectErrors(project.id, options);
660
- const errors = response.body || [];
661
- const totalCount = response.headers.get('X-Total-Count');
662
- const linkHeader = response.headers.get('Link');
663
674
  const result = {
664
- data: errors,
665
- count: errors.length,
666
- total: totalCount ? parseInt(totalCount) : undefined,
667
- next: linkHeader?.match(/<([^>]+)>/)?.[1],
675
+ data: response.body,
676
+ next_url: response.nextUrl ?? undefined,
677
+ data_count: response.body?.length,
678
+ total_count: response.totalCount ?? undefined,
668
679
  };
669
680
  return {
670
681
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -677,20 +688,20 @@ export class BugsnagClient {
677
688
  useCases: [
678
689
  "Discover what filter fields are available before searching for errors",
679
690
  "Find the correct field names for filtering by user, environment, or custom metadata",
680
- "Understand filter options and data types for building complex queries"
691
+ "Understand filter options and data types for building complex queries",
681
692
  ],
682
693
  parameters: [],
683
694
  examples: [
684
695
  {
685
696
  description: "Get all available filter fields",
686
697
  parameters: {},
687
- expectedOutput: "JSON array of EventField objects containing display_id, custom flag, and filter/pivot options"
688
- }
698
+ expectedOutput: "JSON array of EventField objects containing display_id, custom flag, and filter/pivot options",
699
+ },
689
700
  ],
690
701
  hints: [
691
702
  "Use this tool before the List Errors or Get Error tools to understand available filters",
692
- "Look for display_id field in the response - these are the field names to use in filters"
693
- ]
703
+ "Look for display_id field in the response - these are the field names to use in filters",
704
+ ],
694
705
  }, async (_args, _extra) => {
695
706
  const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
696
707
  if (!projectFields)
@@ -706,52 +717,54 @@ export class BugsnagClient {
706
717
  useCases: [
707
718
  "Mark an error as open, fixed or ignored",
708
719
  "Discard or un-discard an error",
709
- "Update the severity of an error"
720
+ "Update the severity of an error",
710
721
  ],
711
722
  parameters: [
712
- ...(this.projectApiKey ? [] : [
713
- {
714
- name: "projectId",
715
- type: z.string(),
716
- description: "ID of the project that contains the error to be updated",
717
- required: true,
718
- }
719
- ]),
723
+ ...(this.projectApiKey
724
+ ? []
725
+ : [
726
+ {
727
+ name: "projectId",
728
+ type: z.string(),
729
+ description: "ID of the project that contains the error to be updated",
730
+ required: true,
731
+ },
732
+ ]),
720
733
  {
721
734
  name: "errorId",
722
735
  type: z.string(),
723
736
  description: "ID of the error to update",
724
737
  required: true,
725
- examples: ["6863e2af8c857c0a5023b411"]
738
+ examples: ["6863e2af8c857c0a5023b411"],
726
739
  },
727
740
  {
728
741
  name: "operation",
729
742
  type: z.enum(PERMITTED_UPDATE_OPERATIONS),
730
743
  description: "The operation to apply to the error",
731
744
  required: true,
732
- examples: ["fix", "open", "ignore", "discard", "undiscard"]
733
- }
745
+ examples: ["fix", "open", "ignore", "discard", "undiscard"],
746
+ },
734
747
  ],
735
748
  examples: [
736
749
  {
737
750
  description: "Mark an error as fixed",
738
751
  parameters: {
739
752
  errorId: "6863e2af8c857c0a5023b411",
740
- operation: "fix"
753
+ operation: "fix",
741
754
  },
742
- expectedOutput: "Success response indicating the error was marked as fixed"
743
- }
755
+ expectedOutput: "Success response indicating the error was marked as fixed",
756
+ },
744
757
  ],
745
758
  hints: [
746
- "Only use valid operations - BugSnag may reject invalid values"
759
+ "Only use valid operations - BugSnag may reject invalid values",
747
760
  ],
748
761
  readOnly: false,
749
762
  idempotent: false,
750
763
  }, async (args, _extra) => {
751
764
  const { errorId, operation } = args;
752
765
  const project = await this.getInputProject(args.projectId);
753
- let severity = undefined;
754
- if (operation === 'override_severity') {
766
+ let severity;
767
+ if (operation === "override_severity") {
755
768
  // illicit the severity from the user
756
769
  const result = await getInput({
757
770
  message: "Please provide the new severity for the error (e.g. 'info', 'warning', 'error', 'critical')",
@@ -760,153 +773,29 @@ export class BugsnagClient {
760
773
  properties: {
761
774
  severity: {
762
775
  type: "string",
763
- enum: ['info', 'warning', 'error'],
764
- description: "The new severity level for the error"
765
- }
766
- }
776
+ enum: ["info", "warning", "error"],
777
+ description: "The new severity level for the error",
778
+ },
779
+ },
767
780
  },
768
- required: ["severity"]
781
+ required: ["severity"],
769
782
  });
770
783
  if (result.action === "accept" && result.content?.severity) {
771
784
  severity = result.content.severity;
772
785
  }
773
786
  }
774
- const result = await this.updateError(project.id, errorId, operation, { severity });
775
- return {
776
- content: [{ type: "text", text: JSON.stringify({ success: result }) }],
777
- };
778
- });
779
- register({
780
- title: "List Builds",
781
- summary: "List builds for a project with optional filtering by release stage",
782
- purpose: "Retrieve a list of build summaries to analyze deployment history and associated errors",
783
- useCases: [
784
- "View recent builds to correlate with error spikes",
785
- "Filter builds by stage (e.g. production, staging) for targeted analysis",
786
- ],
787
- parameters: [
788
- ...(this.projectApiKey
789
- ? []
790
- : [
791
- {
792
- name: "projectId",
793
- type: z.string(),
794
- description: "ID of the project to list builds for",
795
- required: true,
796
- },
797
- ]),
798
- {
799
- name: "releaseStage",
800
- type: z.string(),
801
- description: "Filter builds by this stage (e.g. production, staging)",
802
- required: false,
803
- examples: ["production", "staging"],
804
- },
805
- {
806
- name: "nextUrl",
807
- type: z.string(),
808
- 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.",
809
- required: false,
810
- examples: [
811
- "/projects/515fb9337c1074f6fd000003/builds?offset=30&per_page=30",
812
- ],
813
- },
814
- ],
815
- examples: [
816
- {
817
- description: "List all builds for a project",
818
- parameters: {},
819
- expectedOutput: "JSON array of build objects with metadata",
820
- },
821
- {
822
- description: "List production builds for a project",
823
- parameters: {
824
- releaseStage: "production",
825
- },
826
- expectedOutput: "JSON array of build objects in the production stage",
827
- },
828
- {
829
- description: "Get the next page of results",
830
- parameters: {
831
- nextUrl: "/projects/515fb9337c1074f6fd000003/builds?offset=30&per_page=30",
832
- },
833
- expectedOutput: "JSON array of build objects with metadata from the next page",
834
- }
835
- ],
836
- hints: ["For more detailed results use the Get Build tool"],
837
- readOnly: true,
838
- idempotent: true,
839
- outputFormat: "JSON array of build summary objects with metadata",
840
- }, async (args, _extra) => {
841
- const project = await this.getInputProject(args.projectId);
842
- const { builds, nextUrl } = await this.listBuilds(project.id, {
843
- release_stage: args.releaseStage,
844
- next_url: args.nextUrl,
787
+ const result = await this.updateError(project.id, errorId, operation, {
788
+ severity,
845
789
  });
846
790
  return {
847
791
  content: [
848
- {
849
- type: "text",
850
- text: JSON.stringify({
851
- builds,
852
- next: nextUrl,
853
- }),
854
- }
792
+ { type: "text", text: JSON.stringify({ success: result }) },
855
793
  ],
856
794
  };
857
795
  });
858
- register({
859
- title: "Get Build",
860
- summary: "Get more details for a specific build by its ID",
861
- purpose: "Retrieve detailed information about a build for analysis and debugging",
862
- useCases: [
863
- "View build metadata such as version, source control info, and error counts",
864
- "Analyze a specific build to correlate with error spikes or deployments",
865
- "See the stability targets for a project and if the build meets them",
866
- ],
867
- parameters: [
868
- ...(this.projectApiKey
869
- ? []
870
- : [
871
- {
872
- name: "projectId",
873
- type: z.string(),
874
- description: "ID of the project containing the build",
875
- required: true,
876
- },
877
- ]),
878
- {
879
- name: "buildId",
880
- type: z.string(),
881
- description: "ID of the build to retrieve",
882
- required: true,
883
- examples: ["5f8d0d55c9e77c0017a1b2c3"],
884
- },
885
- ],
886
- examples: [
887
- {
888
- description: "Get details for a specific build",
889
- parameters: {
890
- buildId: "5f8d0d55c9e77c0017a1b2c3",
891
- },
892
- expectedOutput: "JSON object with build details including version, source control info, error counts and stability data.",
893
- },
894
- ],
895
- hints: ["Build IDs can be found using the List builds tool"],
896
- readOnly: true,
897
- idempotent: true,
898
- outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
899
- }, async (args, _extra) => {
900
- if (!args.buildId)
901
- throw new Error("buildId argument is required");
902
- const build = await this.getBuild((await this.getInputProject(args.projectId)).id, args.buildId);
903
- return {
904
- content: [{ type: "text", text: JSON.stringify(build) }],
905
- };
906
- });
907
796
  register({
908
797
  title: "List Releases",
909
- summary: "List releases for a project with optional filtering by release stage",
798
+ summary: "List releases for a project",
910
799
  purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
911
800
  useCases: [
912
801
  "View recent releases to correlate with error spikes",
@@ -925,16 +814,16 @@ export class BugsnagClient {
925
814
  ]),
926
815
  {
927
816
  name: "releaseStage",
928
- type: z.string(),
929
- description: "Filter releases by this stage (e.g. production, staging)",
817
+ type: z.string().default("production"),
818
+ description: "Filter releases by this stage (e.g. production, staging), defaults to 'production'",
930
819
  required: false,
931
820
  examples: ["production", "staging"],
932
821
  },
933
822
  {
934
823
  name: "visibleOnly",
935
- type: z.boolean().default(true),
936
- description: "Whether to only include releases that are marked as visible (default: true)",
937
- required: true,
824
+ type: z.boolean().default(false),
825
+ description: "Whether to only include releases that are marked as visible in the dashboard, defaults to false",
826
+ required: false,
938
827
  examples: ["true", "false"],
939
828
  },
940
829
  {
@@ -949,16 +838,16 @@ export class BugsnagClient {
949
838
  ],
950
839
  examples: [
951
840
  {
952
- description: "List all releases for a project",
841
+ description: "List production releases for a project",
953
842
  parameters: {},
954
- expectedOutput: "JSON array of release objects with metadata",
843
+ expectedOutput: "JSON array of release objects in the production stage",
955
844
  },
956
845
  {
957
- description: "List production releases for a project",
846
+ description: "List staging releases for a project",
958
847
  parameters: {
959
- releaseStage: "production",
848
+ releaseStage: "staging",
960
849
  },
961
- expectedOutput: "JSON array of release objects in the production stage",
850
+ expectedOutput: "JSON array of release objects in the staging stage",
962
851
  },
963
852
  {
964
853
  description: "Get the next page of results",
@@ -968,23 +857,29 @@ export class BugsnagClient {
968
857
  expectedOutput: "JSON array of release objects with metadata from the next page",
969
858
  },
970
859
  ],
971
- hints: ["For more detailed results use the Get Release tool"],
860
+ hints: [
861
+ "Use the Get Release tool to get more details on a specific release, including the builds it contains",
862
+ "The release stage defaults to 'production' if not specified",
863
+ "Use visibleOnly to filter out releases that have been marked as hidden in the dashboard",
864
+ ],
972
865
  readOnly: true,
973
866
  idempotent: true,
974
- outputFormat: "JSON array of release summary objects with metadata",
867
+ outputFormat: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
975
868
  }, async (args, _extra) => {
976
- const { releases, nextUrl } = await this.listReleases((await this.getInputProject(args.projectId)).id, {
977
- release_stage_name: args.releaseStage ?? "production",
869
+ const response = await this.listReleases((await this.getInputProject(args.projectId)).id, {
870
+ release_stage_name: args.releaseStage,
978
871
  visible_only: args.visibleOnly,
979
- next_url: args.nextUrl ?? null,
872
+ next_url: args.nextUrl,
980
873
  });
981
874
  return {
982
875
  content: [
983
876
  {
984
877
  type: "text",
985
878
  text: JSON.stringify({
986
- releases,
987
- next: nextUrl ?? null,
879
+ data: response.body,
880
+ next_url: response.nextUrl ?? undefined,
881
+ data_count: response.body?.length,
882
+ total_count: response.totalCount ?? undefined,
988
883
  }),
989
884
  },
990
885
  ],
@@ -992,12 +887,12 @@ export class BugsnagClient {
992
887
  });
993
888
  register({
994
889
  title: "Get Release",
995
- summary: "Get more details for a specific release by its ID",
890
+ summary: "Get more details for a specific release by its ID, including source control information and associated builds",
996
891
  purpose: "Retrieve detailed information about a release for analysis and debugging",
997
892
  useCases: [
998
893
  "View release metadata such as version, source control info, and error counts",
999
- "Analyze a specific release to correlate with error spikes or deployments",
1000
- "See the stability targets for a project and if the release meets them",
894
+ "Analyze the stability data and targets for a release",
895
+ "See the builds that make up the release",
1001
896
  ],
1002
897
  parameters: [
1003
898
  ...(this.projectApiKey
@@ -1034,18 +929,29 @@ export class BugsnagClient {
1034
929
  }, async (args, _extra) => {
1035
930
  if (!args.releaseId)
1036
931
  throw new Error("releaseId argument is required");
1037
- const release = await this.getRelease((await this.getInputProject(args.projectId)).id, args.releaseId);
932
+ 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);
1038
935
  return {
1039
- content: [{ type: "text", text: JSON.stringify(release) }],
936
+ content: [
937
+ {
938
+ type: "text",
939
+ text: JSON.stringify({
940
+ release: releaseResponse.body,
941
+ builds: buildsResponse.body,
942
+ }),
943
+ },
944
+ ],
1040
945
  };
1041
946
  });
1042
947
  register({
1043
- title: "List Builds in Release",
1044
- summary: "List builds associated with a specific release",
1045
- purpose: "Retrieve a list of builds for a given release to analyze deployment history and associated errors",
948
+ title: "Get Build",
949
+ summary: "Get more details for a specific build by its ID",
950
+ purpose: "Retrieve detailed information about a build for analysis and debugging",
1046
951
  useCases: [
1047
- "View builds within a release to correlate with error spikes",
1048
- "Analyze the composition of a release by examining its builds",
952
+ "View build metadata such as version, source control info, and error counts",
953
+ "Analyze a specific build to correlate with error spikes or deployments",
954
+ "See the stability targets for a project and if the build meets them",
1049
955
  ],
1050
956
  parameters: [
1051
957
  ...(this.projectApiKey
@@ -1054,47 +960,49 @@ export class BugsnagClient {
1054
960
  {
1055
961
  name: "projectId",
1056
962
  type: z.string(),
1057
- description: "ID of the project containing the release",
963
+ description: "ID of the project containing the build",
1058
964
  required: true,
1059
965
  },
1060
966
  ]),
1061
967
  {
1062
- name: "releaseId",
968
+ name: "buildId",
1063
969
  type: z.string(),
1064
- description: "ID of the release to list builds for",
970
+ description: "ID of the build to retrieve",
1065
971
  required: true,
1066
972
  examples: ["5f8d0d55c9e77c0017a1b2c3"],
1067
973
  },
1068
974
  ],
1069
975
  examples: [
1070
976
  {
1071
- description: "List all builds in a specific release",
977
+ description: "Get details for a specific build",
1072
978
  parameters: {
1073
- releaseId: "5f8d0d55c9e77c0017a1b2c3",
979
+ buildId: "5f8d0d55c9e77c0017a1b2c3",
1074
980
  },
1075
- expectedOutput: "JSON array of build objects with metadata",
981
+ expectedOutput: "JSON object with build details including version, source control info, error counts and stability data.",
1076
982
  },
1077
983
  ],
1078
- hints: ["Release IDs can be found using the List releases tool"],
984
+ hints: ["Build IDs can be found using the List builds tool"],
1079
985
  readOnly: true,
1080
986
  idempotent: true,
1081
- outputFormat: "JSON array of build summary objects with metadata",
987
+ outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
1082
988
  }, async (args, _extra) => {
1083
- if (!args.releaseId)
1084
- throw new Error("releaseId argument is required");
1085
- const builds = await this.listBuildsInRelease(args.releaseId);
989
+ 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);
1086
992
  return {
1087
- content: [{ type: "text", text: JSON.stringify(builds) }],
993
+ content: [{ type: "text", text: JSON.stringify(response.body) }],
1088
994
  };
1089
995
  });
1090
996
  }
1091
997
  registerResources(register) {
1092
998
  register("event", "{id}", async (uri, variables, _extra) => {
1093
999
  return {
1094
- contents: [{
1000
+ contents: [
1001
+ {
1095
1002
  uri: uri.href,
1096
- text: JSON.stringify(await this.getEvent(variables.id))
1097
- }]
1003
+ text: JSON.stringify(await this.getEvent(variables.id)),
1004
+ },
1005
+ ],
1098
1006
  };
1099
1007
  });
1100
1008
  }