@smartbear/mcp 0.7.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.
@@ -1,7 +1,6 @@
1
1
  import NodeCache from "node-cache";
2
2
  import { z } from "zod";
3
3
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
4
- import { getNextUrlPathFromHeader } from "./client/api/base.js";
5
4
  import { FilterObjectSchema, toQueryString, } from "./client/api/filters.js";
6
5
  import { ProjectAPI, } from "./client/api/Project.js";
7
6
  import { Configuration, CurrentUserAPI, ErrorAPI } from "./client/index.js";
@@ -13,9 +12,6 @@ const cacheKeys = {
13
12
  PROJECTS: "bugsnag_projects",
14
13
  CURRENT_PROJECT: "bugsnag_current_project",
15
14
  CURRENT_PROJECT_EVENT_FILTERS: "bugsnag_current_project_event_filters",
16
- BUILD: "bugsnag_build", // + buildId
17
- RELEASE: "bugsnag_release", // + releaseId
18
- BUILDS_IN_RELEASE: "bugsnag_builds_in_release", // + releaseId
19
15
  };
20
16
  // Exclude certain event fields from the project event filters to improve agent usage
21
17
  const EXCLUDED_EVENT_FIELDS = new Set([
@@ -216,61 +212,62 @@ export class BugsnagClient {
216
212
  }
217
213
  async listBuilds(projectId, opts) {
218
214
  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 };
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
+ };
224
225
  }
225
226
  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)
227
+ const response = await this.projectApi.getBuild(projectId, buildId);
228
+ if (!response.body)
233
229
  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;
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) };
238
234
  }
239
235
  async listReleases(projectId, opts) {
240
236
  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 };
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
+ };
246
247
  }
247
248
  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)
249
+ const response = await this.projectApi.getRelease(releaseId);
250
+ if (!response.body)
254
251
  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;
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) };
268
256
  }
269
- async getProjectStabilityTargets(projectId) {
270
- 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
+ };
271
269
  }
272
- addStabilityData(source, stabilityTargets) {
273
- const { stability_target_type, target_stability, critical_stability } = stabilityTargets;
270
+ addStabilityData(source, project) {
274
271
  const user_stability = source.accumulative_daily_users_seen === 0 // avoid division by zero
275
272
  ? 0
276
273
  : (source.accumulative_daily_users_seen -
@@ -280,16 +277,18 @@ export class BugsnagClient {
280
277
  ? 0
281
278
  : (source.total_sessions_count - source.unhandled_sessions_count) /
282
279
  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;
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;
286
285
  return {
287
286
  ...source,
288
287
  user_stability,
289
288
  session_stability,
290
- stability_target_type,
291
- target_stability: target_stability.value,
292
- 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,
293
292
  meets_target_stability,
294
293
  meets_critical_stability,
295
294
  };
@@ -307,7 +306,7 @@ export class BugsnagClient {
307
306
  ],
308
307
  parameters: [
309
308
  {
310
- name: "page_size",
309
+ name: "pageSize",
311
310
  type: z.number(),
312
311
  description: "Number of projects to return per page for pagination",
313
312
  required: false,
@@ -325,7 +324,7 @@ export class BugsnagClient {
325
324
  {
326
325
  description: "Get first 10 projects",
327
326
  parameters: {
328
- page_size: 10,
327
+ pageSize: 10,
329
328
  page: 1,
330
329
  },
331
330
  expectedOutput: "JSON array of project objects with IDs, names, and metadata",
@@ -347,8 +346,8 @@ export class BugsnagClient {
347
346
  content: [{ type: "text", text: "No projects found." }],
348
347
  };
349
348
  }
350
- if (args.page_size || args.page) {
351
- const pageSize = args.page_size || 10;
349
+ if (args.pageSize || args.page) {
350
+ const pageSize = args.pageSize || 10;
352
351
  const page = args.page || 1;
353
352
  projects = projects.slice((page - 1) * pageSize, page * pageSize);
354
353
  }
@@ -451,9 +450,7 @@ export class BugsnagClient {
451
450
  // Get the latest event for this error using the events endpoint with filters
452
451
  let latestEvent = null;
453
452
  try {
454
- const eventsResponse = await this.errorsApi.listEventsOnProject(project.id, listEventsQueryString);
455
- const events = eventsResponse.body || [];
456
- latestEvent = events[0] || null;
453
+ latestEvent = (await this.errorsApi.getLatestEventOnProject(project.id, listEventsQueryString)).body;
457
454
  }
458
455
  catch (e) {
459
456
  console.warn("Failed to fetch latest event:", e);
@@ -573,14 +570,14 @@ export class BugsnagClient {
573
570
  examples: ["desc"],
574
571
  },
575
572
  {
576
- name: "per_page",
573
+ name: "perPage",
577
574
  type: z.number().min(1).max(100).default(30),
578
575
  description: "How many results to return per page.",
579
576
  required: false,
580
577
  examples: ["30", "50", "100"],
581
578
  },
582
579
  {
583
- name: "next",
580
+ name: "nextUrl",
584
581
  type: z.string().url(),
585
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.",
586
583
  required: false,
@@ -622,17 +619,17 @@ export class BugsnagClient {
622
619
  },
623
620
  sort: "users",
624
621
  direction: "desc",
625
- per_page: 10,
622
+ perPage: 10,
626
623
  },
627
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",
628
625
  },
629
626
  {
630
627
  description: "Get the next 50 results",
631
628
  parameters: {
632
- next: "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?base=2025-08-29T13%3A11%3A37Z&direction=desc&filters%5Berror.status%5D%5B%5D%5Btype%5D=eq&filters%5Berror.status%5D%5B%5D%5Bvalue%5D=open&offset=10&per_page=10&sort=users",
633
- per_page: 50,
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,
634
631
  },
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",
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",
636
633
  },
637
634
  ],
638
635
  hints: [
@@ -642,8 +639,8 @@ export class BugsnagClient {
642
639
  "Common time filters: event.since (from this time), event.before (until this time)",
643
640
  "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
644
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",
645
- "This tool returns paged results. The 'count' field indicates the number of results returned in the current page, and the 'total' field indicates the total number of results across all pages.",
646
- "If the output contains a 'next' value, there are more results available - call this tool again supplying the next URL as a parameter to retrieve the next page.",
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.",
647
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.",
648
645
  ],
649
646
  }, async (args, _extra) => {
@@ -669,19 +666,16 @@ export class BugsnagClient {
669
666
  options.sort = args.sort;
670
667
  if (args.direction !== undefined)
671
668
  options.direction = args.direction;
672
- if (args.per_page !== undefined)
673
- options.per_page = args.per_page;
674
- if (args.next !== undefined)
675
- options.next = args.next;
669
+ if (args.perPage !== undefined)
670
+ options.per_page = args.perPage;
671
+ if (args.nextUrl !== undefined)
672
+ options.next_url = args.nextUrl;
676
673
  const response = await this.errorsApi.listProjectErrors(project.id, options);
677
- const errors = response.body || [];
678
- const totalCount = response.headers.get("X-Total-Count");
679
- const linkHeader = response.headers.get("Link");
680
674
  const result = {
681
- data: errors,
682
- count: errors.length,
683
- total: totalCount ? parseInt(totalCount, 10) : undefined,
684
- 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,
685
679
  };
686
680
  return {
687
681
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -799,137 +793,9 @@ export class BugsnagClient {
799
793
  ],
800
794
  };
801
795
  });
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
- });
869
- return {
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
796
  register({
931
797
  title: "List Releases",
932
- summary: "List releases for a project with optional filtering by release stage",
798
+ summary: "List releases for a project",
933
799
  purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
934
800
  useCases: [
935
801
  "View recent releases to correlate with error spikes",
@@ -948,16 +814,16 @@ export class BugsnagClient {
948
814
  ]),
949
815
  {
950
816
  name: "releaseStage",
951
- type: z.string(),
952
- 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'",
953
819
  required: false,
954
820
  examples: ["production", "staging"],
955
821
  },
956
822
  {
957
823
  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,
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,
961
827
  examples: ["true", "false"],
962
828
  },
963
829
  {
@@ -972,16 +838,16 @@ export class BugsnagClient {
972
838
  ],
973
839
  examples: [
974
840
  {
975
- description: "List all releases for a project",
841
+ description: "List production releases for a project",
976
842
  parameters: {},
977
- expectedOutput: "JSON array of release objects with metadata",
843
+ expectedOutput: "JSON array of release objects in the production stage",
978
844
  },
979
845
  {
980
- description: "List production releases for a project",
846
+ description: "List staging releases for a project",
981
847
  parameters: {
982
- releaseStage: "production",
848
+ releaseStage: "staging",
983
849
  },
984
- expectedOutput: "JSON array of release objects in the production stage",
850
+ expectedOutput: "JSON array of release objects in the staging stage",
985
851
  },
986
852
  {
987
853
  description: "Get the next page of results",
@@ -991,23 +857,29 @@ export class BugsnagClient {
991
857
  expectedOutput: "JSON array of release objects with metadata from the next page",
992
858
  },
993
859
  ],
994
- 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
+ ],
995
865
  readOnly: true,
996
866
  idempotent: true,
997
- 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",
998
868
  }, async (args, _extra) => {
999
- const { releases, nextUrl } = await this.listReleases((await this.getInputProject(args.projectId)).id, {
1000
- release_stage_name: args.releaseStage ?? "production",
869
+ const response = await this.listReleases((await this.getInputProject(args.projectId)).id, {
870
+ release_stage_name: args.releaseStage,
1001
871
  visible_only: args.visibleOnly,
1002
- next_url: args.nextUrl ?? null,
872
+ next_url: args.nextUrl,
1003
873
  });
1004
874
  return {
1005
875
  content: [
1006
876
  {
1007
877
  type: "text",
1008
878
  text: JSON.stringify({
1009
- releases,
1010
- 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,
1011
883
  }),
1012
884
  },
1013
885
  ],
@@ -1015,12 +887,12 @@ export class BugsnagClient {
1015
887
  });
1016
888
  register({
1017
889
  title: "Get Release",
1018
- 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",
1019
891
  purpose: "Retrieve detailed information about a release for analysis and debugging",
1020
892
  useCases: [
1021
893
  "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",
894
+ "Analyze the stability data and targets for a release",
895
+ "See the builds that make up the release",
1024
896
  ],
1025
897
  parameters: [
1026
898
  ...(this.projectApiKey
@@ -1057,18 +929,29 @@ export class BugsnagClient {
1057
929
  }, async (args, _extra) => {
1058
930
  if (!args.releaseId)
1059
931
  throw new Error("releaseId argument is required");
1060
- 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);
1061
935
  return {
1062
- 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
+ ],
1063
945
  };
1064
946
  });
1065
947
  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",
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",
1069
951
  useCases: [
1070
- "View builds within a release to correlate with error spikes",
1071
- "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",
1072
955
  ],
1073
956
  parameters: [
1074
957
  ...(this.projectApiKey
@@ -1077,37 +960,37 @@ export class BugsnagClient {
1077
960
  {
1078
961
  name: "projectId",
1079
962
  type: z.string(),
1080
- description: "ID of the project containing the release",
963
+ description: "ID of the project containing the build",
1081
964
  required: true,
1082
965
  },
1083
966
  ]),
1084
967
  {
1085
- name: "releaseId",
968
+ name: "buildId",
1086
969
  type: z.string(),
1087
- description: "ID of the release to list builds for",
970
+ description: "ID of the build to retrieve",
1088
971
  required: true,
1089
972
  examples: ["5f8d0d55c9e77c0017a1b2c3"],
1090
973
  },
1091
974
  ],
1092
975
  examples: [
1093
976
  {
1094
- description: "List all builds in a specific release",
977
+ description: "Get details for a specific build",
1095
978
  parameters: {
1096
- releaseId: "5f8d0d55c9e77c0017a1b2c3",
979
+ buildId: "5f8d0d55c9e77c0017a1b2c3",
1097
980
  },
1098
- 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.",
1099
982
  },
1100
983
  ],
1101
- hints: ["Release IDs can be found using the List releases tool"],
984
+ hints: ["Build IDs can be found using the List builds tool"],
1102
985
  readOnly: true,
1103
986
  idempotent: true,
1104
- 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",
1105
988
  }, async (args, _extra) => {
1106
- if (!args.releaseId)
1107
- throw new Error("releaseId argument is required");
1108
- 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);
1109
992
  return {
1110
- content: [{ type: "text", text: JSON.stringify(builds) }],
993
+ content: [{ type: "text", text: JSON.stringify(response.body) }],
1111
994
  };
1112
995
  });
1113
996
  }
@@ -1,5 +1,5 @@
1
1
  import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodString, ZodUnion, } from "zod";
2
+ import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodOptional, ZodString, ZodUnion, } from "zod";
3
3
  import Bugsnag from "../common/bugsnag.js";
4
4
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
5
5
  export class SmartBearMcpServer extends McpServer {
@@ -142,6 +142,9 @@ export class SmartBearMcpServer extends McpServer {
142
142
  `${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join("\n - ")}` : ""}`);
143
143
  }
144
144
  getReadableTypeName(zodType) {
145
+ if (zodType instanceof ZodOptional) {
146
+ zodType = zodType._def.innerType;
147
+ }
145
148
  if (zodType instanceof ZodString)
146
149
  return "string";
147
150
  if (zodType instanceof ZodNumber)
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { SmartBearMcpServer } from "./common/server.js";
7
7
  import { PactflowClient } from "./pactflow/client.js";
8
8
  import { QmetryClient } from "./qmetry/client.js";
9
9
  import { ReflectClient } from "./reflect/client.js";
10
+ import { ZephyrClient } from "./zephyr/client.js";
10
11
  // This is used to report errors in the MCP server itself
11
12
  // If you want to use your own BugSnag API key, set the MCP_SERVER_BUGSNAG_API_KEY environment variable
12
13
  const McpServerBugsnagAPIKey = process.env.MCP_SERVER_BUGSNAG_API_KEY;
@@ -24,6 +25,8 @@ async function main() {
24
25
  const pactBrokerPassword = process.env.PACT_BROKER_PASSWORD;
25
26
  const qmetryToken = process.env.QMETRY_API_KEY;
26
27
  const qmetryBaseUrl = process.env.QMETRY_BASE_URL;
28
+ const zephyrToken = process.env.ZEPHYR_API_TOKEN;
29
+ const zephyrBaseUrl = process.env.ZEPHYR_BASE_URL;
27
30
  let client_defined = false;
28
31
  if (reflectToken) {
29
32
  server.addClient(new ReflectClient(reflectToken));
@@ -56,8 +59,12 @@ async function main() {
56
59
  server.addClient(new QmetryClient(qmetryToken, qmetryBaseUrl));
57
60
  client_defined = true;
58
61
  }
62
+ if (zephyrToken) {
63
+ server.addClient(new ZephyrClient(zephyrToken, zephyrBaseUrl));
64
+ client_defined = true;
65
+ }
59
66
  if (!client_defined) {
60
- console.error("Please set one of REFLECT_API_TOKEN, BUGSNAG_AUTH_TOKEN, API_HUB_API_KEY, QMETRY_API_KEY or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
67
+ console.error("Please set one of REFLECT_API_TOKEN, BUGSNAG_AUTH_TOKEN, API_HUB_API_KEY, QMETRY_API_KEY, ZEPHYR_API_TOKEN, or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
61
68
  process.exit(1);
62
69
  }
63
70
  const transport = new StdioServerTransport();