@smartbear/mcp 0.4.0 → 0.6.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 (31) hide show
  1. package/README.md +15 -121
  2. package/dist/{insight-hub → bugsnag}/client/api/CurrentUser.js +4 -4
  3. package/dist/{insight-hub → bugsnag}/client/api/Error.js +37 -4
  4. package/dist/bugsnag/client/api/Project.js +163 -0
  5. package/dist/{insight-hub → bugsnag}/client/api/base.js +39 -11
  6. package/dist/{insight-hub → bugsnag}/client/api/filters.js +2 -2
  7. package/dist/{insight-hub → bugsnag}/client.js +511 -29
  8. package/dist/common/info.js +1 -1
  9. package/dist/common/server.js +17 -5
  10. package/dist/index.js +11 -11
  11. package/dist/pactflow/client/ai.js +56 -6
  12. package/dist/pactflow/client/base.js +19 -1
  13. package/dist/pactflow/client/prompt-utils.js +89 -0
  14. package/dist/pactflow/client/prompts.js +133 -0
  15. package/dist/pactflow/client/tools.js +43 -2
  16. package/dist/pactflow/client/utils.js +70 -0
  17. package/dist/pactflow/client.js +192 -13
  18. package/package.json +9 -4
  19. package/dist/insight-hub/client/api/Project.js +0 -46
  20. package/dist/package.json +0 -60
  21. package/dist/tests/unit/common/server.test.js +0 -319
  22. package/dist/tests/unit/insight-hub/api-utilities.test.js +0 -31
  23. package/dist/tests/unit/insight-hub/client.test.js +0 -852
  24. package/dist/tests/unit/insight-hub/filters.test.js +0 -93
  25. package/dist/tests/unit/pactflow/ai.test.js +0 -21
  26. package/dist/tests/unit/pactflow/client.test.js +0 -67
  27. package/dist/tests/unit/pactflow/tools.test.js +0 -34
  28. package/dist/vitest.config.js +0 -57
  29. /package/dist/{insight-hub → bugsnag}/client/api/index.js +0 -0
  30. /package/dist/{insight-hub → bugsnag}/client/configuration.js +0 -0
  31. /package/dist/{insight-hub → bugsnag}/client/index.js +0 -0
@@ -2,16 +2,20 @@ 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
4
  import { CurrentUserAPI, ErrorAPI, Configuration } from "./client/index.js";
5
- import { ProjectAPI } from "./client/api/Project.js";
6
5
  import { FilterObjectSchema, toQueryString } from "./client/api/filters.js";
6
+ import { ProjectAPI, } from "./client/api/Project.js";
7
+ import { getNextUrlPathFromHeader } from "./client/api/base.js";
7
8
  const HUB_PREFIX = "00000";
8
9
  const DEFAULT_DOMAIN = "bugsnag.com";
9
- const HUB_DOMAIN = "insighthub.smartbear.com";
10
+ const HUB_DOMAIN = "bugsnag.smartbear.com";
10
11
  const cacheKeys = {
11
- ORG: "insight_hub_org",
12
- PROJECTS: "insight_hub_projects",
13
- CURRENT_PROJECT: "insight_hub_current_project",
14
- CURRENT_PROJECT_EVENT_FILTERS: "insight_hub_current_project_event_filters",
12
+ ORG: "bugsnag_org",
13
+ PROJECTS: "bugsnag_projects",
14
+ CURRENT_PROJECT: "bugsnag_current_project",
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([
@@ -25,7 +29,7 @@ const PERMITTED_UPDATE_OPERATIONS = [
25
29
  "discard",
26
30
  "undiscard"
27
31
  ];
28
- export class InsightHubClient {
32
+ export class BugsnagClient {
29
33
  currentUserApi;
30
34
  errorsApi;
31
35
  cache;
@@ -33,8 +37,8 @@ export class InsightHubClient {
33
37
  projectApiKey;
34
38
  apiEndpoint;
35
39
  appEndpoint;
36
- name = "Insight Hub";
37
- prefix = "insight_hub";
40
+ name = "BugSnag";
41
+ prefix = "bugsnag";
38
42
  constructor(token, projectApiKey, endpoint) {
39
43
  this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
40
44
  this.appEndpoint = this.getEndpoint("app", projectApiKey, endpoint);
@@ -50,14 +54,32 @@ export class InsightHubClient {
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
85
  if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
@@ -127,10 +149,7 @@ export class InsightHubClient {
127
149
  let projects = this.cache.get(cacheKeys.PROJECTS);
128
150
  if (!projects) {
129
151
  const org = await this.getOrganization();
130
- const options = {
131
- paginate: true
132
- };
133
- const response = await this.currentUserApi.getOrganizationProjects(org.id, options);
152
+ const response = await this.currentUserApi.getOrganizationProjects(org.id);
134
153
  projects = response.body || [];
135
154
  this.cache.set(cacheKeys.PROJECTS, projects);
136
155
  }
@@ -192,6 +211,83 @@ export class InsightHubClient {
192
211
  return currentProject;
193
212
  }
194
213
  }
214
+ async listBuilds(projectId, opts) {
215
+ 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 };
221
+ }
222
+ 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)
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;
234
+ }
235
+ async listReleases(projectId, opts) {
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 };
242
+ }
243
+ 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)
250
+ 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;
264
+ }
265
+ async getProjectStabilityTargets(projectId) {
266
+ return await this.projectApi.getProjectStabilityTargets(projectId);
267
+ }
268
+ addStabilityData(source, stabilityTargets) {
269
+ const { stability_target_type, target_stability, critical_stability } = stabilityTargets;
270
+ const user_stability = source.accumulative_daily_users_seen === 0 // avoid division by zero
271
+ ? 0
272
+ : (source.accumulative_daily_users_seen - source.accumulative_daily_users_with_unhandled) /
273
+ source.accumulative_daily_users_seen;
274
+ const session_stability = source.total_sessions_count === 0 // avoid division by zero
275
+ ? 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;
280
+ return {
281
+ ...source,
282
+ user_stability,
283
+ session_stability,
284
+ stability_target_type,
285
+ target_stability: target_stability.value,
286
+ critical_stability: critical_stability.value,
287
+ meets_target_stability,
288
+ meets_critical_stability,
289
+ };
290
+ }
195
291
  registerTools(register, getInput) {
196
292
  if (!this.projectApiKey) {
197
293
  register({
@@ -318,7 +414,7 @@ export class InsightHubClient {
318
414
  }
319
415
  ],
320
416
  hints: [
321
- "Error IDs can be found using the List Errors tool",
417
+ "Error IDs can be found using the List Project Errors tool",
322
418
  "Use this after filtering errors to get detailed information about specific errors",
323
419
  "If you used a filter to get this error, you can pass the same filters here to restrict the results or apply further filters",
324
420
  "The URL provided in the response points should be shown to the user in all cases as it allows them to view the error in the dashboard and perform further analysis",
@@ -378,7 +474,7 @@ export class InsightHubClient {
378
474
  {
379
475
  name: "link",
380
476
  type: z.string(),
381
- description: "Full URL to the event details page in the Insight Hub dashboard (web interface)",
477
+ description: "Full URL to the event details page in the BugSnag dashboard (web interface)",
382
478
  required: true,
383
479
  examples: [
384
480
  "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
@@ -399,7 +495,7 @@ export class InsightHubClient {
399
495
  ],
400
496
  hints: [
401
497
  "The URL must contain both project slug in the path and event_id in query parameters",
402
- "This is useful when users share Insight Hub dashboard URLs and you need to extract the event data"
498
+ "This is useful when users share BugSnag dashboard URLs and you need to extract the event data"
403
499
  ]
404
500
  }, async (args, _extra) => {
405
501
  if (!args.link)
@@ -420,10 +516,9 @@ export class InsightHubClient {
420
516
  content: [{ type: "text", text: JSON.stringify(response) }],
421
517
  };
422
518
  });
423
- // Dynamically infer the filters schema from cached project event fields
424
519
  register({
425
520
  title: "List Project Errors",
426
- summary: "List and search errors in a project using customizable filters",
521
+ summary: "List and search errors in a project using customizable filters and pagination",
427
522
  purpose: "Retrieve filtered list of errors from a project for analysis, debugging, and reporting",
428
523
  useCases: [
429
524
  "Debug recent application errors by filtering for open errors in the last 7 days",
@@ -434,8 +529,11 @@ export class InsightHubClient {
434
529
  parameters: [
435
530
  {
436
531
  name: "filters",
437
- type: FilterObjectSchema,
438
- description: "Apply filters to narrow down the error list. Use the List Errors or Get Error tools to discover available filter fields",
532
+ type: FilterObjectSchema.default({
533
+ "event.since": [{ type: "eq", value: "30d" }],
534
+ "error.status": [{ type: "eq", value: "open" }]
535
+ }),
536
+ description: "Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields",
439
537
  required: false,
440
538
  examples: [
441
539
  '{"error.status": [{"type": "eq", "value": "open"}]}',
@@ -449,6 +547,35 @@ export class InsightHubClient {
449
547
  "Relative time periods: h (hours), d (days)"
450
548
  ]
451
549
  },
550
+ {
551
+ name: "sort",
552
+ type: z.enum(["first_seen", "last_seen", "events", "users", "unsorted"]).default("last_seen"),
553
+ description: "Field to sort the errors by",
554
+ required: false,
555
+ examples: ["last_seen"]
556
+ },
557
+ {
558
+ name: "direction",
559
+ type: z.enum(["asc", "desc"]).default("desc"),
560
+ description: "Sort direction for ordering results",
561
+ required: false,
562
+ examples: ["desc"]
563
+ },
564
+ {
565
+ name: "per_page",
566
+ type: z.number().min(1).max(100).default(30),
567
+ description: "How many results to return per page.",
568
+ required: false,
569
+ examples: ["30", "50", "100"]
570
+ },
571
+ {
572
+ name: "next",
573
+ type: z.string().url(),
574
+ 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
+ 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."]
578
+ },
452
579
  ...(this.projectApiKey ? [] : [
453
580
  {
454
581
  name: "projectId",
@@ -467,7 +594,28 @@ export class InsightHubClient {
467
594
  "event.since": [{ "type": "eq", "value": "24h" }]
468
595
  }
469
596
  },
470
- expectedOutput: "JSON object with a list of errors in the 'data' field, and an error count in the 'count' field"
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"
598
+ },
599
+ {
600
+ description: "Get the 10 open errors with the most users affected in the last 30 days",
601
+ parameters: {
602
+ filters: {
603
+ "event.since": [{ "type": "eq", "value": "30d" }],
604
+ "error.status": [{ "type": "eq", "value": "open" }]
605
+ },
606
+ sort: "users",
607
+ direction: "desc",
608
+ per_page: 10
609
+ },
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"
611
+ },
612
+ {
613
+ description: "Get the next 50 results",
614
+ 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
617
+ },
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"
471
619
  }
472
620
  ],
473
621
  hints: [
@@ -475,13 +623,17 @@ export class InsightHubClient {
475
623
  "Combine multiple filters to narrow results - filters are applied with AND logic",
476
624
  "For time filters: use relative format (7d, 24h) for recent periods or ISO 8601 UTC format (2018-05-20T00:00:00Z) for specific dates",
477
625
  "Common time filters: event.since (from this time), event.before (until this time)",
478
- "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"
626
+ "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
627
+ "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."
479
631
  ]
480
632
  }, async (args, _extra) => {
481
633
  const project = await this.getInputProject(args.projectId);
482
- // Optionally, validate filter keys against cached event fields
483
- const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
634
+ // Validate filter keys against cached event fields
484
635
  if (args.filters) {
636
+ const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
485
637
  const validKeys = new Set(eventFields.map(f => f.display_id));
486
638
  for (const key of Object.keys(args.filters)) {
487
639
  if (!validKeys.has(key)) {
@@ -489,11 +641,30 @@ export class InsightHubClient {
489
641
  }
490
642
  }
491
643
  }
492
- const response = await this.errorsApi.listProjectErrors(project.id, { filters: args.filters });
644
+ const defaultFilters = {
645
+ "event.since": [{ "type": "eq", "value": "30d" }],
646
+ "error.status": [{ "type": "eq", "value": "open" }]
647
+ };
648
+ const options = {
649
+ filters: { ...defaultFilters, ...args.filters }
650
+ };
651
+ if (args.sort !== undefined)
652
+ options.sort = args.sort;
653
+ if (args.direction !== undefined)
654
+ 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;
659
+ const response = await this.errorsApi.listProjectErrors(project.id, options);
493
660
  const errors = response.body || [];
661
+ const totalCount = response.headers.get('X-Total-Count');
662
+ const linkHeader = response.headers.get('Link');
494
663
  const result = {
495
664
  data: errors,
496
665
  count: errors.length,
666
+ total: totalCount ? parseInt(totalCount) : undefined,
667
+ next: linkHeader?.match(/<([^>]+)>/)?.[1],
497
668
  };
498
669
  return {
499
670
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -572,7 +743,7 @@ export class InsightHubClient {
572
743
  }
573
744
  ],
574
745
  hints: [
575
- "Only use valid operations - Insight Hub may reject invalid values"
746
+ "Only use valid operations - BugSnag may reject invalid values"
576
747
  ],
577
748
  readOnly: false,
578
749
  idempotent: false,
@@ -605,6 +776,317 @@ export class InsightHubClient {
605
776
  content: [{ type: "text", text: JSON.stringify({ success: result }) }],
606
777
  };
607
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,
845
+ });
846
+ return {
847
+ content: [
848
+ {
849
+ type: "text",
850
+ text: JSON.stringify({
851
+ builds,
852
+ next: nextUrl,
853
+ }),
854
+ }
855
+ ],
856
+ };
857
+ });
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
+ register({
908
+ title: "List Releases",
909
+ summary: "List releases for a project with optional filtering by release stage",
910
+ purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
911
+ useCases: [
912
+ "View recent releases to correlate with error spikes",
913
+ "Filter releases by stage (e.g. production, staging) for targeted analysis",
914
+ ],
915
+ parameters: [
916
+ ...(this.projectApiKey
917
+ ? []
918
+ : [
919
+ {
920
+ name: "projectId",
921
+ type: z.string(),
922
+ description: "ID of the project to list releases for",
923
+ required: true,
924
+ },
925
+ ]),
926
+ {
927
+ name: "releaseStage",
928
+ type: z.string(),
929
+ description: "Filter releases by this stage (e.g. production, staging)",
930
+ required: false,
931
+ examples: ["production", "staging"],
932
+ },
933
+ {
934
+ 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,
938
+ examples: ["true", "false"],
939
+ },
940
+ {
941
+ name: "nextUrl",
942
+ type: z.string(),
943
+ 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.",
944
+ required: false,
945
+ examples: [
946
+ "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30",
947
+ ],
948
+ },
949
+ ],
950
+ examples: [
951
+ {
952
+ description: "List all releases for a project",
953
+ parameters: {},
954
+ expectedOutput: "JSON array of release objects with metadata",
955
+ },
956
+ {
957
+ description: "List production releases for a project",
958
+ parameters: {
959
+ releaseStage: "production",
960
+ },
961
+ expectedOutput: "JSON array of release objects in the production stage",
962
+ },
963
+ {
964
+ description: "Get the next page of results",
965
+ parameters: {
966
+ nextUrl: "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30",
967
+ },
968
+ expectedOutput: "JSON array of release objects with metadata from the next page",
969
+ },
970
+ ],
971
+ hints: ["For more detailed results use the Get Release tool"],
972
+ readOnly: true,
973
+ idempotent: true,
974
+ outputFormat: "JSON array of release summary objects with metadata",
975
+ }, async (args, _extra) => {
976
+ const { releases, nextUrl } = await this.listReleases((await this.getInputProject(args.projectId)).id, {
977
+ release_stage_name: args.releaseStage ?? "production",
978
+ visible_only: args.visibleOnly,
979
+ next_url: args.nextUrl ?? null,
980
+ });
981
+ return {
982
+ content: [
983
+ {
984
+ type: "text",
985
+ text: JSON.stringify({
986
+ releases,
987
+ next: nextUrl ?? null,
988
+ }),
989
+ },
990
+ ],
991
+ };
992
+ });
993
+ register({
994
+ title: "Get Release",
995
+ summary: "Get more details for a specific release by its ID",
996
+ purpose: "Retrieve detailed information about a release for analysis and debugging",
997
+ useCases: [
998
+ "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",
1001
+ ],
1002
+ parameters: [
1003
+ ...(this.projectApiKey
1004
+ ? []
1005
+ : [
1006
+ {
1007
+ name: "projectId",
1008
+ type: z.string(),
1009
+ description: "ID of the project containing the release",
1010
+ required: true,
1011
+ },
1012
+ ]),
1013
+ {
1014
+ name: "releaseId",
1015
+ type: z.string(),
1016
+ description: "ID of the release to retrieve",
1017
+ required: true,
1018
+ examples: ["5f8d0d55c9e77c0017a1b2c3"],
1019
+ },
1020
+ ],
1021
+ examples: [
1022
+ {
1023
+ description: "Get details for a specific release",
1024
+ parameters: {
1025
+ releaseId: "5f8d0d55c9e77c0017a1b2c3",
1026
+ },
1027
+ expectedOutput: "JSON object with release details including version, source control info, error counts and stability data.",
1028
+ },
1029
+ ],
1030
+ hints: ["Release IDs can be found using the List releases tool"],
1031
+ readOnly: true,
1032
+ idempotent: true,
1033
+ outputFormat: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
1034
+ }, async (args, _extra) => {
1035
+ if (!args.releaseId)
1036
+ throw new Error("releaseId argument is required");
1037
+ const release = await this.getRelease((await this.getInputProject(args.projectId)).id, args.releaseId);
1038
+ return {
1039
+ content: [{ type: "text", text: JSON.stringify(release) }],
1040
+ };
1041
+ });
1042
+ 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",
1046
+ useCases: [
1047
+ "View builds within a release to correlate with error spikes",
1048
+ "Analyze the composition of a release by examining its builds",
1049
+ ],
1050
+ parameters: [
1051
+ ...(this.projectApiKey
1052
+ ? []
1053
+ : [
1054
+ {
1055
+ name: "projectId",
1056
+ type: z.string(),
1057
+ description: "ID of the project containing the release",
1058
+ required: true,
1059
+ },
1060
+ ]),
1061
+ {
1062
+ name: "releaseId",
1063
+ type: z.string(),
1064
+ description: "ID of the release to list builds for",
1065
+ required: true,
1066
+ examples: ["5f8d0d55c9e77c0017a1b2c3"],
1067
+ },
1068
+ ],
1069
+ examples: [
1070
+ {
1071
+ description: "List all builds in a specific release",
1072
+ parameters: {
1073
+ releaseId: "5f8d0d55c9e77c0017a1b2c3",
1074
+ },
1075
+ expectedOutput: "JSON array of build objects with metadata",
1076
+ },
1077
+ ],
1078
+ hints: ["Release IDs can be found using the List releases tool"],
1079
+ readOnly: true,
1080
+ idempotent: true,
1081
+ outputFormat: "JSON array of build summary objects with metadata",
1082
+ }, async (args, _extra) => {
1083
+ if (!args.releaseId)
1084
+ throw new Error("releaseId argument is required");
1085
+ const builds = await this.listBuildsInRelease(args.releaseId);
1086
+ return {
1087
+ content: [{ type: "text", text: JSON.stringify(builds) }],
1088
+ };
1089
+ });
608
1090
  }
609
1091
  registerResources(register) {
610
1092
  register("event", "{id}", async (uri, variables, _extra) => {