@smartbear/mcp 0.5.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.
@@ -2,8 +2,9 @@ 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
10
  const HUB_DOMAIN = "bugsnag.smartbear.com";
@@ -12,6 +13,9 @@ 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([
@@ -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,14 +54,32 @@ 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
85
  if (apiKey && apiKey.startsWith(HUB_PREFIX)) {
@@ -127,10 +149,7 @@ export class BugsnagClient {
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 BugsnagClient {
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({
@@ -420,7 +516,6 @@ export class BugsnagClient {
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
521
  summary: "List and search errors in a project using customizable filters and pagination",
@@ -434,7 +529,10 @@ export class BugsnagClient {
434
529
  parameters: [
435
530
  {
436
531
  name: "filters",
437
- type: FilterObjectSchema,
532
+ type: FilterObjectSchema.default({
533
+ "event.since": [{ type: "eq", value: "30d" }],
534
+ "error.status": [{ type: "eq", value: "open" }]
535
+ }),
438
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: [
@@ -451,8 +549,8 @@ export class BugsnagClient {
451
549
  },
452
550
  {
453
551
  name: "sort",
454
- type: z.enum(["first_seen", "last_seen", "events", "users", "unsorted"]),
455
- description: "Field to sort the errors by (default: last_seen)",
552
+ type: z.enum(["first_seen", "last_seen", "events", "users", "unsorted"]).default("last_seen"),
553
+ description: "Field to sort the errors by",
456
554
  required: false,
457
555
  examples: ["last_seen"]
458
556
  },
@@ -465,7 +563,7 @@ export class BugsnagClient {
465
563
  },
466
564
  {
467
565
  name: "per_page",
468
- type: z.number().min(1).max(100),
566
+ type: z.number().min(1).max(100).default(30),
469
567
  description: "How many results to return per page.",
470
568
  required: false,
471
569
  examples: ["30", "50", "100"]
@@ -525,6 +623,7 @@ export class BugsnagClient {
525
623
  "Combine multiple filters to narrow results - filters are applied with AND logic",
526
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",
527
625
  "Common time filters: event.since (from this time), event.before (until this time)",
626
+ "The 'event.since' filter and 'error.status' filters are always applied and if not specified are set to '30d' and 'open' respectively",
528
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",
529
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.",
530
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.",
@@ -542,9 +641,13 @@ export class BugsnagClient {
542
641
  }
543
642
  }
544
643
  }
545
- const options = {};
546
- if (args.filters)
547
- options.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
+ };
548
651
  if (args.sort !== undefined)
549
652
  options.sort = args.sort;
550
653
  if (args.direction !== undefined)
@@ -673,6 +776,317 @@ export class BugsnagClient {
673
776
  content: [{ type: "text", text: JSON.stringify({ success: result }) }],
674
777
  };
675
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
+ });
676
1090
  }
677
1091
  registerResources(register) {
678
1092
  register("event", "{id}", async (uri, variables, _extra) => {
@@ -11,6 +11,10 @@ export class SmartBearMcpServer extends McpServer {
11
11
  capabilities: {
12
12
  resources: { listChanged: true }, // Server supports dynamic resource lists
13
13
  tools: { listChanged: true }, // Server supports dynamic tool lists
14
+ sampling: {}, // Server supports sampling requests to Host
15
+ elicitation: {}, // Server supports eliciting input from the user
16
+ logging: {}, // Server supports logging messages
17
+ prompts: {}, // Server supports sending prompts to Host
14
18
  },
15
19
  });
16
20
  }
@@ -48,6 +52,11 @@ export class SmartBearMcpServer extends McpServer {
48
52
  });
49
53
  });
50
54
  }
55
+ if (client.registerPrompts) {
56
+ client.registerPrompts((name, config, cb) => {
57
+ return super.registerPrompt(name, config, cb);
58
+ });
59
+ }
51
60
  }
52
61
  getAnnotations(toolTitle, params) {
53
62
  const annotations = {
package/dist/index.js CHANGED
@@ -38,11 +38,11 @@ async function main() {
38
38
  }
39
39
  if (pactBrokerUrl) {
40
40
  if (pactBrokerToken) {
41
- server.addClient(new PactflowClient(pactBrokerToken, pactBrokerUrl, "pactflow"));
41
+ server.addClient(new PactflowClient(pactBrokerToken, pactBrokerUrl, "pactflow", server.server));
42
42
  client_defined = true;
43
43
  }
44
44
  else if (pactBrokerUsername && pactBrokerPassword) {
45
- server.addClient(new PactflowClient({ username: pactBrokerUsername, password: pactBrokerPassword }, pactBrokerUrl, "pact_broker"));
45
+ server.addClient(new PactflowClient({ username: pactBrokerUsername, password: pactBrokerPassword }, pactBrokerUrl, "pact_broker", server.server));
46
46
  client_defined = true;
47
47
  }
48
48
  else {
@@ -78,7 +78,7 @@ export const EndpointMatcherSchema = z
78
78
  .optional()
79
79
  .describe("OpenAPI operation ID to match (e.g., 'getUserById', 'get*'). Supports glob patterns"),
80
80
  })
81
- .required()
81
+ .optional()
82
82
  .describe("REQUIRED: Matcher to specify which endpoints from the OpenAPI document to generate tests for. At least one matcher field must be provided");
83
83
  export const RemoteOpenAPIDocumentSchema = z
84
84
  .object({
@@ -104,7 +104,8 @@ export const OpenAPIWithMatcherSchema = z
104
104
  matcher: EndpointMatcherSchema,
105
105
  remoteDocument: RemoteOpenAPIDocumentSchema.optional().describe("The remote OpenAPI document to use for the review/generation in case openapi document is not provided. If provided do not include the document field under openapi."),
106
106
  })
107
- .describe("If provided, the OpenAPI document which describes the API being tested and is accompanied by a matcher which will be used to identify the interactions in the OpenAPI document which are relevant to the Pact refinement process.").transform(addOpenAPISpecToSchema);
107
+ .describe("If provided, the OpenAPI document which describes the API being tested and is accompanied by a matcher which will be used to identify the interactions in the OpenAPI document which are relevant to the Pact refinement process.")
108
+ .transform(addOpenAPISpecToSchema);
108
109
  export const RefineInputSchema = z.object({
109
110
  pactTests: FileInputSchema.describe("Primary pact tests that needs to be refined."),
110
111
  code: z
@@ -144,3 +145,33 @@ export const GenerationInputSchema = z.object({
144
145
  .describe("Optional free-form instructions to guide the generation process (e.g., 'Focus on error scenarios', 'Include authentication headers', 'Use specific test framework patterns')"),
145
146
  testTemplate: FileInputSchema.optional().describe("Optional test template to use as a basis for generation. Helps ensure generated tests follow your specific patterns, frameworks, and coding standards"),
146
147
  });
148
+ export const MatcherRecommendationInputSchema = z.array(EndpointMatcherSchema);
149
+ export const AiCreditsSchema = z.object({
150
+ total: z
151
+ .number()
152
+ .describe("The total number of AI credits available."),
153
+ used: z
154
+ .number()
155
+ .describe("The number of AI credits used."),
156
+ }).describe("AI credits information.");
157
+ export const OrganizationEntitlementsSchema = z.object({
158
+ name: z
159
+ .string()
160
+ .describe("The name of the organization."),
161
+ planAiEnabled: z
162
+ .boolean()
163
+ .describe("Whether AI features are enabled at the plan level."),
164
+ preferencesAiEnabled: z
165
+ .boolean()
166
+ .describe("Whether AI features are enabled at the preferences level."),
167
+ aiCredits: AiCreditsSchema.describe("AI credits information."),
168
+ }).describe("Organization entitlements information.");
169
+ export const UserEntitlementsSchema = z.object({
170
+ aiPermissions: z
171
+ .array(z.string())
172
+ .describe("List of AI permissions."),
173
+ }).describe("User entitlements information.");
174
+ export const EntitlementsSchema = z.object({
175
+ organizationEntitlements: OrganizationEntitlementsSchema.describe("Organization entitlements information."),
176
+ userEntitlements: UserEntitlementsSchema.describe("User entitlements information."),
177
+ }).describe("Entitlements information.");
@@ -1,6 +1,19 @@
1
1
  import { z } from "zod";
2
2
  export const CanIDeploySchema = z.object({
3
- pacticipant: z.string(),
4
- version: z.string(),
5
- environment: z.string(),
3
+ pacticipant: z.string().describe("The name of the pacticipant (application/service) being evaluated for deployment"),
4
+ version: z.string().describe("The version of the pacticipant that you want to check if it's safe to deploy"),
5
+ environment: z.string().describe("The target environment where the pacticipant version will be deployed (e.g., 'production', 'staging', 'test')"),
6
+ });
7
+ export const MatrixSchema = z.object({
8
+ latestby: z.string().optional().describe("This property removes the rows for the overridden pacts/verifications from the results. The options are cvp (show only the latest row for each consumer version and provider) and cvpv (show only the latest row each consumer version and provider version). For a can-i-deploy query with one selector, it should be set to cvp. For a can-i-deploy query with two selectors, it should be set to cvpv."),
9
+ limit: z.number().min(1).max(1000).default(100).optional().describe("The limit on the number of results to return (1-1000, default: 100)"),
10
+ q: z.array(z.object({
11
+ pacticipant: z.string().describe("Name of the pacticipant (application)"),
12
+ version: z.string().optional().describe("Version number"),
13
+ branch: z.string().optional().describe("Name of the pacticipant version branch"),
14
+ environment: z.string().optional().describe("The name of the environment that the pacticipant version is deployed to"),
15
+ latest: z.boolean().optional().describe("Used in conjunction with other properties to indicate whether the selector is describing the latest version from a branch/with a tag/for a pacticipant, or all of them. Note that when used with tags, the 'latest' is calculated using the creation date of the pacticipant version, NOT the creation date of the tag."),
16
+ tag: z.string().optional().describe("The name of the pacticipant version tag (superseded by branch and environments)"),
17
+ mainBranch: z.boolean().optional().describe("Whether or not the version(s) described are from the main branch of the pacticipant, as set in the mainBranch property of the pacticipant resource."),
18
+ })).min(1).max(2)
6
19
  });