@smartbear/mcp 0.5.0 → 0.7.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 (36) hide show
  1. package/README.md +22 -111
  2. package/dist/api-hub/client/api.js +253 -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 +55 -0
  7. package/dist/api-hub/client/tools.js +86 -0
  8. package/dist/api-hub/client.js +64 -404
  9. package/dist/bugsnag/client/api/CurrentUser.js +18 -13
  10. package/dist/bugsnag/client/api/Error.js +35 -35
  11. package/dist/bugsnag/client/api/Project.js +137 -9
  12. package/dist/bugsnag/client/api/base.js +27 -13
  13. package/dist/bugsnag/client/api/filters.js +9 -9
  14. package/dist/bugsnag/client.js +584 -145
  15. package/dist/common/info.js +1 -1
  16. package/dist/common/server.js +44 -27
  17. package/dist/index.js +13 -6
  18. package/dist/pactflow/client/ai.js +33 -2
  19. package/dist/pactflow/client/base.js +51 -3
  20. package/dist/pactflow/client/prompt-utils.js +89 -0
  21. package/dist/pactflow/client/prompts.js +131 -0
  22. package/dist/pactflow/client/tools.js +39 -7
  23. package/dist/pactflow/client/utils.js +1 -1
  24. package/dist/pactflow/client.js +147 -9
  25. package/dist/qmetry/client/api/client-api.js +39 -0
  26. package/dist/qmetry/client/handlers.js +11 -0
  27. package/dist/qmetry/client/project.js +27 -0
  28. package/dist/qmetry/client/testcase.js +104 -0
  29. package/dist/qmetry/client/tools.js +222 -0
  30. package/dist/qmetry/client.js +95 -0
  31. package/dist/qmetry/config/constants.js +12 -0
  32. package/dist/qmetry/config/rest-endpoints.js +11 -0
  33. package/dist/qmetry/types/common.js +174 -0
  34. package/dist/qmetry/types/testcase.js +19 -0
  35. package/dist/reflect/client.js +14 -14
  36. package/package.json +9 -6
@@ -1,9 +1,10 @@
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 { ProjectAPI } from "./client/api/Project.js";
6
- import { FilterObjectSchema, toQueryString } from "./client/api/filters.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";
7
8
  const HUB_PREFIX = "00000";
8
9
  const DEFAULT_DOMAIN = "bugsnag.com";
9
10
  const HUB_DOMAIN = "bugsnag.smartbear.com";
@@ -12,10 +13,13 @@ const cacheKeys = {
12
13
  PROJECTS: "bugsnag_projects",
13
14
  CURRENT_PROJECT: "bugsnag_current_project",
14
15
  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
15
19
  };
16
20
  // Exclude certain event fields from the project event filters to improve agent usage
17
21
  const EXCLUDED_EVENT_FIELDS = new Set([
18
- "search" // This is searches multiple fields and is more a convenience for humans, we're removing to avoid over-matching
22
+ "search", // This is searches multiple fields and is more a convenience for humans, we're removing to avoid over-matching
19
23
  ]);
20
24
  const PERMITTED_UPDATE_OPERATIONS = [
21
25
  "override_severity",
@@ -23,7 +27,7 @@ const PERMITTED_UPDATE_OPERATIONS = [
23
27
  "fix",
24
28
  "ignore",
25
29
  "discard",
26
- "undiscard"
30
+ "undiscard",
27
31
  ];
28
32
  export class BugsnagClient {
29
33
  currentUserApi;
@@ -33,7 +37,7 @@ export class BugsnagClient {
33
37
  projectApiKey;
34
38
  apiEndpoint;
35
39
  appEndpoint;
36
- name = "Bugsnag";
40
+ name = "BugSnag";
37
41
  prefix = "bugsnag";
38
42
  constructor(token, projectApiKey, endpoint) {
39
43
  this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
@@ -50,17 +54,35 @@ export class BugsnagClient {
50
54
  });
51
55
  this.currentUserApi = new CurrentUserAPI(config);
52
56
  this.errorsApi = new ErrorAPI(config);
53
- this.cache = new NodeCache();
57
+ this.cache = new NodeCache({
58
+ stdTTL: 24 * 60 * 60, // default cache TTL of 24 hours
59
+ });
54
60
  this.projectApi = new ProjectAPI(config);
55
61
  this.projectApiKey = projectApiKey;
56
62
  }
57
63
  async initialize() {
58
64
  // Trigger caching of org and projects
59
- await this.getProjects();
60
- await this.getCurrentProject();
65
+ try {
66
+ await this.getProjects();
67
+ }
68
+ catch (error) {
69
+ // Swallow auth errors here to allow the tools to be registered for visibility, even if the token is invalid
70
+ console.error("Unable to connect to BugSnag APIs, the BugSnag tools will not work. Check your configured BugSnag auth token.", error);
71
+ }
72
+ if (this.projectApiKey) {
73
+ try {
74
+ await this.getCurrentProject();
75
+ }
76
+ catch (error) {
77
+ // Clear the project API key to allow tools to work across all projects
78
+ this.projectApiKey = undefined;
79
+ console.error("Unable to find your configured BugSnag project, the BugSnag tools will continue to work across all projects in your organization. " +
80
+ "Check your configured BugSnag project API key.", error);
81
+ }
82
+ }
61
83
  }
62
84
  getHost(apiKey, subdomain) {
63
- if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
85
+ if (apiKey?.startsWith(HUB_PREFIX)) {
64
86
  return `https://${subdomain}.${HUB_DOMAIN}`;
65
87
  }
66
88
  else {
@@ -73,7 +95,7 @@ export class BugsnagClient {
73
95
  getEndpoint(subdomain, apiKey, endpoint) {
74
96
  let subDomainEndpoint;
75
97
  if (!endpoint) {
76
- if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
98
+ if (apiKey?.startsWith(HUB_PREFIX)) {
77
99
  subDomainEndpoint = `https://${subdomain}.${HUB_DOMAIN}`;
78
100
  }
79
101
  else {
@@ -83,7 +105,8 @@ export class BugsnagClient {
83
105
  else {
84
106
  // check if the endpoint matches either the HUB_DOMAIN or DEFAULT_DOMAIN
85
107
  const url = new URL(endpoint);
86
- if (url.hostname.endsWith(HUB_DOMAIN) || url.hostname.endsWith(DEFAULT_DOMAIN)) {
108
+ if (url.hostname.endsWith(HUB_DOMAIN) ||
109
+ url.hostname.endsWith(DEFAULT_DOMAIN)) {
87
110
  // For known domains (Hub or Bugsnag), always use HTTPS and standard format
88
111
  if (url.hostname.endsWith(HUB_DOMAIN)) {
89
112
  subDomainEndpoint = `https://${subdomain}.${HUB_DOMAIN}`;
@@ -102,7 +125,7 @@ export class BugsnagClient {
102
125
  async getDashboardUrl(project) {
103
126
  return `${this.appEndpoint}/${(await this.getOrganization()).slug}/${project.slug}`;
104
127
  }
105
- async getErrorUrl(project, errorId, queryString = '') {
128
+ async getErrorUrl(project, errorId, queryString = "") {
106
129
  const dashboardUrl = await this.getDashboardUrl(project);
107
130
  return `${dashboardUrl}/errors/${errorId}${queryString}`;
108
131
  }
@@ -127,10 +150,7 @@ export class BugsnagClient {
127
150
  let projects = this.cache.get(cacheKeys.PROJECTS);
128
151
  if (!projects) {
129
152
  const org = await this.getOrganization();
130
- const options = {
131
- paginate: true
132
- };
133
- const response = await this.currentUserApi.getOrganizationProjects(org.id, options);
153
+ const response = await this.currentUserApi.getOrganizationProjects(org.id);
134
154
  projects = response.body || [];
135
155
  this.cache.set(cacheKeys.PROJECTS, projects);
136
156
  }
@@ -146,7 +166,7 @@ export class BugsnagClient {
146
166
  const projects = await this.getProjects();
147
167
  project = projects.find((p) => p.api_key === this.projectApiKey) ?? null;
148
168
  if (!project) {
149
- throw new Error(`Unable to find project with API key ${this.projectApiKey} in organization.`);
169
+ throw new Error("Unable to find project with the configured API key.");
150
170
  }
151
171
  this.cache.set(cacheKeys.CURRENT_PROJECT, project);
152
172
  if (project) {
@@ -160,24 +180,26 @@ export class BugsnagClient {
160
180
  if (!filtersResponse || filtersResponse.length === 0) {
161
181
  throw new Error(`No event fields found for project ${project.name}.`);
162
182
  }
163
- filtersResponse = filtersResponse.filter(field => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
183
+ filtersResponse = filtersResponse.filter((field) => !EXCLUDED_EVENT_FIELDS.has(field.display_id));
164
184
  return filtersResponse;
165
185
  }
166
186
  async getEvent(eventId, projectId) {
167
- const projectIds = projectId ? [projectId] : (await this.getProjects()).map((p) => p.id);
168
- const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch(_e => null)));
169
- return projectEvents.find(event => event && !!event.body)?.body || null;
187
+ const projectIds = projectId
188
+ ? [projectId]
189
+ : (await this.getProjects()).map((p) => p.id);
190
+ const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch((_e) => null)));
191
+ return projectEvents.find((event) => event && !!event.body)?.body || null;
170
192
  }
171
193
  async updateError(projectId, errorId, operation, options) {
172
194
  const errorUpdateRequest = {
173
195
  operation: operation,
174
- ...options
196
+ ...options,
175
197
  };
176
198
  const response = await this.errorsApi.updateErrorOnProject(projectId, errorId, errorUpdateRequest);
177
199
  return response.status === 200 || response.status === 204;
178
200
  }
179
201
  async getInputProject(projectId) {
180
- if (typeof projectId === 'string') {
202
+ if (typeof projectId === "string") {
181
203
  const maybeProject = await this.getProject(projectId);
182
204
  if (!maybeProject) {
183
205
  throw new Error(`Project with ID ${projectId} not found.`);
@@ -187,11 +209,91 @@ export class BugsnagClient {
187
209
  else {
188
210
  const currentProject = await this.getCurrentProject();
189
211
  if (!currentProject) {
190
- throw new Error('No current project found. Please provide a projectId or configure a project API key.');
212
+ throw new Error("No current project found. Please provide a projectId or configure a project API key.");
191
213
  }
192
214
  return currentProject;
193
215
  }
194
216
  }
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
275
+ ? 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
280
+ ? 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;
286
+ return {
287
+ ...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,
295
+ };
296
+ }
195
297
  registerTools(register, getInput) {
196
298
  if (!this.projectApiKey) {
197
299
  register({
@@ -201,7 +303,7 @@ export class BugsnagClient {
201
303
  useCases: [
202
304
  "Browse available projects when no specific project API key is configured",
203
305
  "Find project IDs needed for other tools",
204
- "Get an overview of all projects in the organization"
306
+ "Get an overview of all projects in the organization",
205
307
  ],
206
308
  parameters: [
207
309
  {
@@ -209,35 +311,35 @@ export class BugsnagClient {
209
311
  type: z.number(),
210
312
  description: "Number of projects to return per page for pagination",
211
313
  required: false,
212
- examples: ["10", "25", "50"]
314
+ examples: ["10", "25", "50"],
213
315
  },
214
316
  {
215
317
  name: "page",
216
318
  type: z.number(),
217
319
  description: "Page number to return (starts from 1)",
218
320
  required: false,
219
- examples: ["1", "2", "3"]
220
- }
321
+ examples: ["1", "2", "3"],
322
+ },
221
323
  ],
222
324
  examples: [
223
325
  {
224
326
  description: "Get first 10 projects",
225
327
  parameters: {
226
328
  page_size: 10,
227
- page: 1
329
+ page: 1,
228
330
  },
229
331
  expectedOutput: "JSON array of project objects with IDs, names, and metadata",
230
332
  },
231
333
  {
232
334
  description: "Get all projects (no pagination)",
233
335
  parameters: {},
234
- expectedOutput: "JSON array of all available projects"
235
- }
336
+ expectedOutput: "JSON array of all available projects",
337
+ },
236
338
  ],
237
339
  hints: [
238
340
  "Use pagination for organizations with many projects to avoid large responses",
239
- "Project IDs from this list can be used with other tools when no project API key is configured"
240
- ]
341
+ "Project IDs from this list can be used with other tools when no project API key is configured",
342
+ ],
241
343
  }, async (args, _extra) => {
242
344
  let projects = await this.getProjects();
243
345
  if (!projects || projects.length === 0) {
@@ -267,7 +369,7 @@ export class BugsnagClient {
267
369
  "Investigate a specific error found through the List Project Errors tool",
268
370
  "Understand which types of user are affected by the error using summarized event data",
269
371
  "Get error details for debugging and root cause analysis",
270
- "Retrieve error metadata for incident reports and documentation"
372
+ "Retrieve error metadata for incident reports and documentation",
271
373
  ],
272
374
  parameters: [
273
375
  {
@@ -275,16 +377,18 @@ export class BugsnagClient {
275
377
  type: z.string(),
276
378
  required: true,
277
379
  description: "Unique identifier of the error to retrieve",
278
- examples: ["6863e2af8c857c0a5023b411"]
380
+ examples: ["6863e2af8c857c0a5023b411"],
279
381
  },
280
- ...(this.projectApiKey ? [] : [
281
- {
282
- name: "projectId",
283
- type: z.string(),
284
- required: true,
285
- description: "ID of the project containing the error",
286
- }
287
- ]),
382
+ ...(this.projectApiKey
383
+ ? []
384
+ : [
385
+ {
386
+ name: "projectId",
387
+ type: z.string(),
388
+ required: true,
389
+ description: "ID of the project containing the error",
390
+ },
391
+ ]),
288
392
  {
289
393
  name: "filters",
290
394
  type: FilterObjectSchema,
@@ -294,14 +398,14 @@ export class BugsnagClient {
294
398
  '{"error.status": [{"type": "eq", "value": "open"}]}',
295
399
  '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
296
400
  '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
297
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}'
401
+ '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
298
402
  ],
299
403
  constraints: [
300
404
  "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
301
405
  "ISO 8601 times must be in UTC and use extended format",
302
- "Relative time periods: h (hours), d (days)"
303
- ]
304
- }
406
+ "Relative time periods: h (hours), d (days)",
407
+ ],
408
+ },
305
409
  ],
306
410
  outputFormat: "JSON object containing: " +
307
411
  " - error_details: Aggregated data about the error, including first and last seen occurrence" +
@@ -312,10 +416,10 @@ export class BugsnagClient {
312
416
  {
313
417
  description: "Get details for a specific error",
314
418
  parameters: {
315
- errorId: "6863e2af8c857c0a5023b411"
419
+ errorId: "6863e2af8c857c0a5023b411",
316
420
  },
317
- expectedOutput: "JSON object with error details including message, stack trace, occurrence count, and metadata"
318
- }
421
+ expectedOutput: "JSON object with error details including message, stack trace, occurrence count, and metadata",
422
+ },
319
423
  ],
320
424
  hints: [
321
425
  "Error IDs can be found using the List Project Errors tool",
@@ -334,13 +438,13 @@ export class BugsnagClient {
334
438
  // Build query parameters
335
439
  const params = new URLSearchParams();
336
440
  // Add sorting and pagination parameters to get the latest event
337
- params.append('sort', 'timestamp');
338
- params.append('direction', 'desc');
339
- params.append('per_page', '1');
340
- params.append('full_reports', 'true');
441
+ params.append("sort", "timestamp");
442
+ params.append("direction", "desc");
443
+ params.append("per_page", "1");
444
+ params.append("full_reports", "true");
341
445
  const filters = {
342
- "error": [{ type: "eq", value: args.errorId }],
343
- ...args.filters
446
+ error: [{ type: "eq", value: args.errorId }],
447
+ ...args.filters,
344
448
  };
345
449
  const filtersQueryString = toQueryString(filters);
346
450
  const listEventsQueryString = `?${params}&${filtersQueryString}`;
@@ -358,11 +462,12 @@ export class BugsnagClient {
358
462
  const content = {
359
463
  error_details: errorDetails,
360
464
  latest_event: latestEvent,
361
- pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId)).body || [],
465
+ pivots: (await this.errorsApi.listErrorPivots(project.id, args.errorId))
466
+ .body || [],
362
467
  url: await this.getErrorUrl(project, args.errorId, `?${filtersQueryString}`),
363
468
  };
364
469
  return {
365
- content: [{ type: "text", text: JSON.stringify(content) }]
470
+ content: [{ type: "text", text: JSON.stringify(content) }],
366
471
  };
367
472
  });
368
473
  register({
@@ -372,7 +477,7 @@ export class BugsnagClient {
372
477
  useCases: [
373
478
  "Get event details when given a dashboard URL from a user or notification",
374
479
  "Extract event information from shared links or browser URLs",
375
- "Quick lookup of event details without needing separate project and event IDs"
480
+ "Quick lookup of event details without needing separate project and event IDs",
376
481
  ],
377
482
  parameters: [
378
483
  {
@@ -381,32 +486,32 @@ export class BugsnagClient {
381
486
  description: "Full URL to the event details page in the BugSnag dashboard (web interface)",
382
487
  required: true,
383
488
  examples: [
384
- "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
489
+ "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000",
385
490
  ],
386
491
  constraints: [
387
- "Must be a valid dashboard URL containing project slug and event_id parameter"
388
- ]
389
- }
492
+ "Must be a valid dashboard URL containing project slug and event_id parameter",
493
+ ],
494
+ },
390
495
  ],
391
496
  examples: [
392
497
  {
393
498
  description: "Get event details from a dashboard URL",
394
499
  parameters: {
395
- link: "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
500
+ link: "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000",
396
501
  },
397
- expectedOutput: "JSON object with complete event details including stack trace, metadata, and context"
398
- }
502
+ expectedOutput: "JSON object with complete event details including stack trace, metadata, and context",
503
+ },
399
504
  ],
400
505
  hints: [
401
506
  "The URL must contain both project slug in the path and event_id in query parameters",
402
- "This is useful when users share BugSnag dashboard URLs and you need to extract the event data"
403
- ]
507
+ "This is useful when users share BugSnag dashboard URLs and you need to extract the event data",
508
+ ],
404
509
  }, async (args, _extra) => {
405
510
  if (!args.link)
406
511
  throw new Error("link argument is required");
407
512
  const url = new URL(args.link);
408
513
  const eventId = url.searchParams.get("event_id");
409
- const projectSlug = url.pathname.split('/')[2];
514
+ const projectSlug = url.pathname.split("/")[2];
410
515
  if (!projectSlug || !eventId)
411
516
  throw new Error("Both projectSlug and eventId must be present in the link");
412
517
  // get the project id from list of projects
@@ -420,7 +525,6 @@ export class BugsnagClient {
420
525
  content: [{ type: "text", text: JSON.stringify(response) }],
421
526
  };
422
527
  });
423
- // Dynamically infer the filters schema from cached project event fields
424
528
  register({
425
529
  title: "List Project Errors",
426
530
  summary: "List and search errors in a project using customizable filters and pagination",
@@ -429,122 +533,138 @@ export class BugsnagClient {
429
533
  "Debug recent application errors by filtering for open errors in the last 7 days",
430
534
  "Generate error reports for stakeholders by filtering specific error types or severity levels",
431
535
  "Monitor error trends over time using date range filters",
432
- "Find errors affecting specific users or environments using metadata filters"
536
+ "Find errors affecting specific users or environments using metadata filters",
433
537
  ],
434
538
  parameters: [
435
539
  {
436
540
  name: "filters",
437
- type: FilterObjectSchema,
541
+ type: FilterObjectSchema.default({
542
+ "event.since": [{ type: "eq", value: "30d" }],
543
+ "error.status": [{ type: "eq", value: "open" }],
544
+ }),
438
545
  description: "Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields",
439
546
  required: false,
440
547
  examples: [
441
548
  '{"error.status": [{"type": "eq", "value": "open"}]}',
442
549
  '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
443
550
  '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
444
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}'
551
+ '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
445
552
  ],
446
553
  constraints: [
447
554
  "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
448
555
  "ISO 8601 times must be in UTC and use extended format",
449
- "Relative time periods: h (hours), d (days)"
450
- ]
556
+ "Relative time periods: h (hours), d (days)",
557
+ ],
451
558
  },
452
559
  {
453
560
  name: "sort",
454
- type: z.enum(["first_seen", "last_seen", "events", "users", "unsorted"]),
455
- description: "Field to sort the errors by (default: last_seen)",
561
+ type: z
562
+ .enum(["first_seen", "last_seen", "events", "users", "unsorted"])
563
+ .default("last_seen"),
564
+ description: "Field to sort the errors by",
456
565
  required: false,
457
- examples: ["last_seen"]
566
+ examples: ["last_seen"],
458
567
  },
459
568
  {
460
569
  name: "direction",
461
570
  type: z.enum(["asc", "desc"]).default("desc"),
462
571
  description: "Sort direction for ordering results",
463
572
  required: false,
464
- examples: ["desc"]
573
+ examples: ["desc"],
465
574
  },
466
575
  {
467
576
  name: "per_page",
468
- type: z.number().min(1).max(100),
577
+ type: z.number().min(1).max(100).default(30),
469
578
  description: "How many results to return per page.",
470
579
  required: false,
471
- examples: ["30", "50", "100"]
580
+ examples: ["30", "50", "100"],
472
581
  },
473
582
  {
474
583
  name: "next",
475
584
  type: z.string().url(),
476
585
  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.",
477
586
  required: false,
478
- examples: ["https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?offset=30&per_page=30&sort=last_seen"],
479
- constraints: ["Only values provided in the output from this tool can be used. Do not attempt to construct it manually."]
587
+ examples: [
588
+ "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?offset=30&per_page=30&sort=last_seen",
589
+ ],
590
+ constraints: [
591
+ "Only values provided in the output from this tool can be used. Do not attempt to construct it manually.",
592
+ ],
480
593
  },
481
- ...(this.projectApiKey ? [] : [
482
- {
483
- name: "projectId",
484
- type: z.string(),
485
- description: "ID of the project to query for errors",
486
- required: true,
487
- }
488
- ])
594
+ ...(this.projectApiKey
595
+ ? []
596
+ : [
597
+ {
598
+ name: "projectId",
599
+ type: z.string(),
600
+ description: "ID of the project to query for errors",
601
+ required: true,
602
+ },
603
+ ]),
489
604
  ],
490
605
  examples: [
491
606
  {
492
607
  description: "Find errors affecting a specific user in the last 24 hours",
493
608
  parameters: {
494
609
  filters: {
495
- "user.email": [{ "type": "eq", "value": "user@example.com" }],
496
- "event.since": [{ "type": "eq", "value": "24h" }]
497
- }
610
+ "user.email": [{ type: "eq", value: "user@example.com" }],
611
+ "event.since": [{ type: "eq", value: "24h" }],
612
+ },
498
613
  },
499
- 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"
614
+ 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",
500
615
  },
501
616
  {
502
617
  description: "Get the 10 open errors with the most users affected in the last 30 days",
503
618
  parameters: {
504
619
  filters: {
505
- "event.since": [{ "type": "eq", "value": "30d" }],
506
- "error.status": [{ "type": "eq", "value": "open" }]
620
+ "event.since": [{ type: "eq", value: "30d" }],
621
+ "error.status": [{ type: "eq", value: "open" }],
507
622
  },
508
623
  sort: "users",
509
624
  direction: "desc",
510
- per_page: 10
625
+ per_page: 10,
511
626
  },
512
- 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"
627
+ 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",
513
628
  },
514
629
  {
515
630
  description: "Get the next 50 results",
516
631
  parameters: {
517
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",
518
- per_page: 50
633
+ per_page: 50,
519
634
  },
520
- 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"
521
- }
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",
636
+ },
522
637
  ],
523
638
  hints: [
524
639
  "Use list_project_event_filters tool first to discover valid filter field names for your project",
525
640
  "Combine multiple filters to narrow results - filters are applied with AND logic",
526
641
  "For time filters: use relative format (7d, 24h) for recent periods or ISO 8601 UTC format (2018-05-20T00:00:00Z) for specific dates",
527
642
  "Common time filters: event.since (from this time), event.before (until this time)",
643
+ "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
528
644
  "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",
529
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.",
530
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.",
531
- "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."
532
- ]
647
+ "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
+ ],
533
649
  }, async (args, _extra) => {
534
650
  const project = await this.getInputProject(args.projectId);
535
651
  // Validate filter keys against cached event fields
536
652
  if (args.filters) {
537
653
  const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
538
- const validKeys = new Set(eventFields.map(f => f.display_id));
654
+ const validKeys = new Set(eventFields.map((f) => f.display_id));
539
655
  for (const key of Object.keys(args.filters)) {
540
656
  if (!validKeys.has(key)) {
541
657
  throw new Error(`Invalid filter key: ${key}`);
542
658
  }
543
659
  }
544
660
  }
545
- const options = {};
546
- if (args.filters)
547
- options.filters = args.filters;
661
+ const defaultFilters = {
662
+ "event.since": [{ type: "eq", value: "30d" }],
663
+ "error.status": [{ type: "eq", value: "open" }],
664
+ };
665
+ const options = {
666
+ filters: { ...defaultFilters, ...args.filters },
667
+ };
548
668
  if (args.sort !== undefined)
549
669
  options.sort = args.sort;
550
670
  if (args.direction !== undefined)
@@ -555,12 +675,12 @@ export class BugsnagClient {
555
675
  options.next = args.next;
556
676
  const response = await this.errorsApi.listProjectErrors(project.id, options);
557
677
  const errors = response.body || [];
558
- const totalCount = response.headers.get('X-Total-Count');
559
- const linkHeader = response.headers.get('Link');
678
+ const totalCount = response.headers.get("X-Total-Count");
679
+ const linkHeader = response.headers.get("Link");
560
680
  const result = {
561
681
  data: errors,
562
682
  count: errors.length,
563
- total: totalCount ? parseInt(totalCount) : undefined,
683
+ total: totalCount ? parseInt(totalCount, 10) : undefined,
564
684
  next: linkHeader?.match(/<([^>]+)>/)?.[1],
565
685
  };
566
686
  return {
@@ -574,20 +694,20 @@ export class BugsnagClient {
574
694
  useCases: [
575
695
  "Discover what filter fields are available before searching for errors",
576
696
  "Find the correct field names for filtering by user, environment, or custom metadata",
577
- "Understand filter options and data types for building complex queries"
697
+ "Understand filter options and data types for building complex queries",
578
698
  ],
579
699
  parameters: [],
580
700
  examples: [
581
701
  {
582
702
  description: "Get all available filter fields",
583
703
  parameters: {},
584
- expectedOutput: "JSON array of EventField objects containing display_id, custom flag, and filter/pivot options"
585
- }
704
+ expectedOutput: "JSON array of EventField objects containing display_id, custom flag, and filter/pivot options",
705
+ },
586
706
  ],
587
707
  hints: [
588
708
  "Use this tool before the List Errors or Get Error tools to understand available filters",
589
- "Look for display_id field in the response - these are the field names to use in filters"
590
- ]
709
+ "Look for display_id field in the response - these are the field names to use in filters",
710
+ ],
591
711
  }, async (_args, _extra) => {
592
712
  const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
593
713
  if (!projectFields)
@@ -603,52 +723,54 @@ export class BugsnagClient {
603
723
  useCases: [
604
724
  "Mark an error as open, fixed or ignored",
605
725
  "Discard or un-discard an error",
606
- "Update the severity of an error"
726
+ "Update the severity of an error",
607
727
  ],
608
728
  parameters: [
609
- ...(this.projectApiKey ? [] : [
610
- {
611
- name: "projectId",
612
- type: z.string(),
613
- description: "ID of the project that contains the error to be updated",
614
- required: true,
615
- }
616
- ]),
729
+ ...(this.projectApiKey
730
+ ? []
731
+ : [
732
+ {
733
+ name: "projectId",
734
+ type: z.string(),
735
+ description: "ID of the project that contains the error to be updated",
736
+ required: true,
737
+ },
738
+ ]),
617
739
  {
618
740
  name: "errorId",
619
741
  type: z.string(),
620
742
  description: "ID of the error to update",
621
743
  required: true,
622
- examples: ["6863e2af8c857c0a5023b411"]
744
+ examples: ["6863e2af8c857c0a5023b411"],
623
745
  },
624
746
  {
625
747
  name: "operation",
626
748
  type: z.enum(PERMITTED_UPDATE_OPERATIONS),
627
749
  description: "The operation to apply to the error",
628
750
  required: true,
629
- examples: ["fix", "open", "ignore", "discard", "undiscard"]
630
- }
751
+ examples: ["fix", "open", "ignore", "discard", "undiscard"],
752
+ },
631
753
  ],
632
754
  examples: [
633
755
  {
634
756
  description: "Mark an error as fixed",
635
757
  parameters: {
636
758
  errorId: "6863e2af8c857c0a5023b411",
637
- operation: "fix"
759
+ operation: "fix",
638
760
  },
639
- expectedOutput: "Success response indicating the error was marked as fixed"
640
- }
761
+ expectedOutput: "Success response indicating the error was marked as fixed",
762
+ },
641
763
  ],
642
764
  hints: [
643
- "Only use valid operations - BugSnag may reject invalid values"
765
+ "Only use valid operations - BugSnag may reject invalid values",
644
766
  ],
645
767
  readOnly: false,
646
768
  idempotent: false,
647
769
  }, async (args, _extra) => {
648
770
  const { errorId, operation } = args;
649
771
  const project = await this.getInputProject(args.projectId);
650
- let severity = undefined;
651
- if (operation === 'override_severity') {
772
+ let severity;
773
+ if (operation === "override_severity") {
652
774
  // illicit the severity from the user
653
775
  const result = await getInput({
654
776
  message: "Please provide the new severity for the error (e.g. 'info', 'warning', 'error', 'critical')",
@@ -657,30 +779,347 @@ export class BugsnagClient {
657
779
  properties: {
658
780
  severity: {
659
781
  type: "string",
660
- enum: ['info', 'warning', 'error'],
661
- description: "The new severity level for the error"
662
- }
663
- }
782
+ enum: ["info", "warning", "error"],
783
+ description: "The new severity level for the error",
784
+ },
785
+ },
664
786
  },
665
- required: ["severity"]
787
+ required: ["severity"],
666
788
  });
667
789
  if (result.action === "accept" && result.content?.severity) {
668
790
  severity = result.content.severity;
669
791
  }
670
792
  }
671
- const result = await this.updateError(project.id, errorId, operation, { severity });
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,
868
+ });
672
869
  return {
673
- content: [{ type: "text", text: JSON.stringify({ success: result }) }],
870
+ content: [
871
+ {
872
+ type: "text",
873
+ text: JSON.stringify({
874
+ builds,
875
+ next: nextUrl,
876
+ }),
877
+ },
878
+ ],
879
+ };
880
+ });
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
+ register({
931
+ title: "List Releases",
932
+ summary: "List releases for a project with optional filtering by release stage",
933
+ purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
934
+ useCases: [
935
+ "View recent releases to correlate with error spikes",
936
+ "Filter releases by stage (e.g. production, staging) for targeted analysis",
937
+ ],
938
+ parameters: [
939
+ ...(this.projectApiKey
940
+ ? []
941
+ : [
942
+ {
943
+ name: "projectId",
944
+ type: z.string(),
945
+ description: "ID of the project to list releases for",
946
+ required: true,
947
+ },
948
+ ]),
949
+ {
950
+ name: "releaseStage",
951
+ type: z.string(),
952
+ description: "Filter releases by this stage (e.g. production, staging)",
953
+ required: false,
954
+ examples: ["production", "staging"],
955
+ },
956
+ {
957
+ 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,
961
+ examples: ["true", "false"],
962
+ },
963
+ {
964
+ name: "nextUrl",
965
+ type: z.string(),
966
+ 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.",
967
+ required: false,
968
+ examples: [
969
+ "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30",
970
+ ],
971
+ },
972
+ ],
973
+ examples: [
974
+ {
975
+ description: "List all releases for a project",
976
+ parameters: {},
977
+ expectedOutput: "JSON array of release objects with metadata",
978
+ },
979
+ {
980
+ description: "List production releases for a project",
981
+ parameters: {
982
+ releaseStage: "production",
983
+ },
984
+ expectedOutput: "JSON array of release objects in the production stage",
985
+ },
986
+ {
987
+ description: "Get the next page of results",
988
+ parameters: {
989
+ nextUrl: "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30",
990
+ },
991
+ expectedOutput: "JSON array of release objects with metadata from the next page",
992
+ },
993
+ ],
994
+ hints: ["For more detailed results use the Get Release tool"],
995
+ readOnly: true,
996
+ idempotent: true,
997
+ outputFormat: "JSON array of release summary objects with metadata",
998
+ }, 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
+ });
1004
+ return {
1005
+ content: [
1006
+ {
1007
+ type: "text",
1008
+ text: JSON.stringify({
1009
+ releases,
1010
+ next: nextUrl ?? null,
1011
+ }),
1012
+ },
1013
+ ],
1014
+ };
1015
+ });
1016
+ register({
1017
+ title: "Get Release",
1018
+ summary: "Get more details for a specific release by its ID",
1019
+ purpose: "Retrieve detailed information about a release for analysis and debugging",
1020
+ useCases: [
1021
+ "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",
1024
+ ],
1025
+ parameters: [
1026
+ ...(this.projectApiKey
1027
+ ? []
1028
+ : [
1029
+ {
1030
+ name: "projectId",
1031
+ type: z.string(),
1032
+ description: "ID of the project containing the release",
1033
+ required: true,
1034
+ },
1035
+ ]),
1036
+ {
1037
+ name: "releaseId",
1038
+ type: z.string(),
1039
+ description: "ID of the release to retrieve",
1040
+ required: true,
1041
+ examples: ["5f8d0d55c9e77c0017a1b2c3"],
1042
+ },
1043
+ ],
1044
+ examples: [
1045
+ {
1046
+ description: "Get details for a specific release",
1047
+ parameters: {
1048
+ releaseId: "5f8d0d55c9e77c0017a1b2c3",
1049
+ },
1050
+ expectedOutput: "JSON object with release details including version, source control info, error counts and stability data.",
1051
+ },
1052
+ ],
1053
+ hints: ["Release IDs can be found using the List releases tool"],
1054
+ readOnly: true,
1055
+ idempotent: true,
1056
+ outputFormat: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
1057
+ }, async (args, _extra) => {
1058
+ 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);
1061
+ return {
1062
+ content: [{ type: "text", text: JSON.stringify(release) }],
1063
+ };
1064
+ });
1065
+ 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",
1069
+ useCases: [
1070
+ "View builds within a release to correlate with error spikes",
1071
+ "Analyze the composition of a release by examining its builds",
1072
+ ],
1073
+ parameters: [
1074
+ ...(this.projectApiKey
1075
+ ? []
1076
+ : [
1077
+ {
1078
+ name: "projectId",
1079
+ type: z.string(),
1080
+ description: "ID of the project containing the release",
1081
+ required: true,
1082
+ },
1083
+ ]),
1084
+ {
1085
+ name: "releaseId",
1086
+ type: z.string(),
1087
+ description: "ID of the release to list builds for",
1088
+ required: true,
1089
+ examples: ["5f8d0d55c9e77c0017a1b2c3"],
1090
+ },
1091
+ ],
1092
+ examples: [
1093
+ {
1094
+ description: "List all builds in a specific release",
1095
+ parameters: {
1096
+ releaseId: "5f8d0d55c9e77c0017a1b2c3",
1097
+ },
1098
+ expectedOutput: "JSON array of build objects with metadata",
1099
+ },
1100
+ ],
1101
+ hints: ["Release IDs can be found using the List releases tool"],
1102
+ readOnly: true,
1103
+ idempotent: true,
1104
+ outputFormat: "JSON array of build summary objects with metadata",
1105
+ }, async (args, _extra) => {
1106
+ if (!args.releaseId)
1107
+ throw new Error("releaseId argument is required");
1108
+ const builds = await this.listBuildsInRelease(args.releaseId);
1109
+ return {
1110
+ content: [{ type: "text", text: JSON.stringify(builds) }],
674
1111
  };
675
1112
  });
676
1113
  }
677
1114
  registerResources(register) {
678
1115
  register("event", "{id}", async (uri, variables, _extra) => {
679
1116
  return {
680
- contents: [{
1117
+ contents: [
1118
+ {
681
1119
  uri: uri.href,
682
- text: JSON.stringify(await this.getEvent(variables.id))
683
- }]
1120
+ text: JSON.stringify(await this.getEvent(variables.id)),
1121
+ },
1122
+ ],
684
1123
  };
685
1124
  });
686
1125
  }