@smartbear/mcp 0.10.0 → 0.11.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 (35) hide show
  1. package/README.md +10 -8
  2. package/dist/bugsnag/client/api/index.js +2 -0
  3. package/dist/bugsnag/client/filters.js +0 -6
  4. package/dist/bugsnag/client.js +234 -376
  5. package/dist/bugsnag/input-schemas.js +51 -0
  6. package/dist/collaborator/client.js +18 -5
  7. package/dist/common/cache.js +63 -0
  8. package/dist/common/client-registry.js +128 -0
  9. package/dist/common/register-clients.js +31 -0
  10. package/dist/common/server.js +30 -9
  11. package/dist/common/transport-http.js +377 -0
  12. package/dist/common/transport-stdio.js +43 -0
  13. package/dist/index.js +20 -70
  14. package/dist/pactflow/client.js +39 -19
  15. package/dist/qmetry/client.js +24 -9
  16. package/dist/reflect/client.js +10 -4
  17. package/dist/{api-hub → swagger}/client/api.js +2 -2
  18. package/dist/{api-hub → swagger}/client/configuration.js +1 -1
  19. package/dist/{api-hub → swagger}/client/index.js +2 -2
  20. package/dist/{api-hub → swagger}/client/tools.js +4 -4
  21. package/dist/{api-hub → swagger}/client.js +47 -35
  22. package/dist/swagger/config-utils.js +18 -0
  23. package/dist/zephyr/client.js +40 -8
  24. package/dist/zephyr/common/rest-api-schemas.js +79 -78
  25. package/dist/zephyr/tool/priority/get-priorities.js +43 -0
  26. package/dist/zephyr/tool/status/get-statuses.js +49 -0
  27. package/dist/zephyr/tool/test-case/get-test-case.js +39 -0
  28. package/dist/zephyr/tool/test-case/get-test-cases.js +64 -0
  29. package/dist/zephyr/tool/test-cycle/get-test-cycle.js +39 -0
  30. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +2 -2
  31. package/dist/zephyr/tool/test-execution/get-test-execution.js +39 -0
  32. package/package.json +2 -2
  33. /package/dist/{api-hub → swagger}/client/portal-types.js +0 -0
  34. /package/dist/{api-hub → swagger}/client/registry-types.js +0 -0
  35. /package/dist/{api-hub → swagger}/client/user-management-types.js +0 -0
@@ -1,17 +1,17 @@
1
- import NodeCache from "node-cache";
2
1
  import { z } from "zod";
3
2
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
4
3
  import { ToolError, } from "../common/types.js";
5
- import { Configuration, CurrentUserAPI, ErrorAPI, ProjectAPI, } from "./client/api/index.js";
6
- import { FilterObjectSchema, toUrlSearchParams, } from "./client/filters.js";
4
+ import { Configuration, CurrentUserAPI, ErrorAPI, ErrorUpdateRequest, ProjectAPI, } from "./client/api/index.js";
5
+ import { toUrlSearchParams } from "./client/filters.js";
6
+ import { toolInputParameters } from "./input-schemas.js";
7
7
  const HUB_PREFIX = "00000";
8
8
  const DEFAULT_DOMAIN = "bugsnag.com";
9
9
  const HUB_DOMAIN = "bugsnag.smartbear.com";
10
10
  const cacheKeys = {
11
11
  ORG: "bugsnag_org",
12
12
  PROJECTS: "bugsnag_projects",
13
+ PROJECT_EVENT_FILTERS: "bugsnag_project_event_filters",
13
14
  CURRENT_PROJECT: "bugsnag_current_project",
14
- CURRENT_PROJECT_EVENT_FILTERS: "bugsnag_current_project_event_filters",
15
15
  };
16
16
  // Exclude certain event fields from the project event filters to improve agent usage
17
17
  const EXCLUDED_EVENT_FIELDS = new Set([
@@ -25,57 +25,92 @@ const PERMITTED_UPDATE_OPERATIONS = [
25
25
  "discard",
26
26
  "undiscard",
27
27
  ];
28
+ const ConfigurationSchema = z.object({
29
+ auth_token: z.string().describe("BugSnag personal authentication token"),
30
+ project_api_key: z.string().describe("BugSnag project API key").optional(),
31
+ endpoint: z.string().url().describe("BugSnag endpoint URL").optional(),
32
+ });
28
33
  export class BugsnagClient {
29
- currentUserApi;
30
- errorsApi;
31
34
  cache;
32
- projectApi;
33
35
  projectApiKey;
34
- apiEndpoint;
35
- appEndpoint;
36
+ configuredProjectApiKey;
37
+ _currentUserApi;
38
+ _errorsApi;
39
+ _projectApi;
40
+ _appEndpoint;
41
+ get currentUserApi() {
42
+ if (!this._currentUserApi)
43
+ throw new Error("Client not configured");
44
+ return this._currentUserApi;
45
+ }
46
+ get errorsApi() {
47
+ if (!this._errorsApi)
48
+ throw new Error("Client not configured");
49
+ return this._errorsApi;
50
+ }
51
+ get projectApi() {
52
+ if (!this._projectApi)
53
+ throw new Error("Client not configured");
54
+ return this._projectApi;
55
+ }
56
+ get appEndpoint() {
57
+ if (!this._appEndpoint)
58
+ throw new Error("Client not configured");
59
+ return this._appEndpoint;
60
+ }
36
61
  name = "BugSnag";
37
- prefix = "bugsnag";
38
- constructor(token, projectApiKey, endpoint) {
39
- this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
40
- this.appEndpoint = this.getEndpoint("app", projectApiKey, endpoint);
41
- const config = new Configuration({
42
- apiKey: `token ${token}`,
62
+ toolPrefix = "bugsnag";
63
+ configPrefix = "Bugsnag";
64
+ config = ConfigurationSchema;
65
+ async configure(server, config) {
66
+ this.cache = server.getCache();
67
+ this._appEndpoint = this.getEndpoint("app", config.project_api_key, config.endpoint);
68
+ const apiConfig = new Configuration({
69
+ apiKey: `token ${config.auth_token}`,
43
70
  headers: {
44
71
  "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
45
72
  "Content-Type": "application/json",
46
73
  "X-Bugsnag-API": "true",
47
74
  "X-Version": "2",
48
75
  },
49
- basePath: this.apiEndpoint,
76
+ basePath: this.getEndpoint("api", config.project_api_key, config.endpoint),
50
77
  });
51
- this.currentUserApi = new CurrentUserAPI(config);
52
- this.errorsApi = new ErrorAPI(config);
53
- this.cache = new NodeCache({
54
- stdTTL: 24 * 60 * 60, // default cache TTL of 24 hours
55
- });
56
- this.projectApi = new ProjectAPI(config);
57
- this.projectApiKey = projectApiKey;
58
- }
59
- async initialize() {
78
+ this._currentUserApi = new CurrentUserAPI(apiConfig);
79
+ this._errorsApi = new ErrorAPI(apiConfig);
80
+ this._projectApi = new ProjectAPI(apiConfig);
81
+ this.projectApiKey = config.project_api_key;
60
82
  // Trigger caching of org and projects
61
83
  try {
62
- await this.getProjects();
84
+ const projects = await this.getProjects();
85
+ // If there's just one project, make this the current project
86
+ if (projects.length === 1 && !this.projectApiKey) {
87
+ this.projectApiKey = projects[0].apiKey;
88
+ }
63
89
  }
64
90
  catch (error) {
65
91
  // Swallow auth errors here to allow the tools to be registered for visibility, even if the token is invalid
66
92
  console.error("Unable to connect to BugSnag APIs, the BugSnag tools will not work. Check your configured BugSnag auth token.", error);
67
93
  }
68
94
  if (this.projectApiKey) {
95
+ this.configuredProjectApiKey = this.projectApiKey; // Store the originally configured API key
96
+ let currentProject = null;
69
97
  try {
70
- await this.getCurrentProject();
98
+ currentProject = await this.getCurrentProject();
71
99
  }
72
100
  catch (error) {
101
+ console.error("An error occurred while fetching project information", error);
102
+ }
103
+ if (currentProject) {
104
+ await this.getProjectEventFilters(currentProject);
105
+ }
106
+ else {
73
107
  // Clear the project API key to allow tools to work across all projects
74
108
  this.projectApiKey = undefined;
75
109
  console.error("Unable to find your configured BugSnag project, the BugSnag tools will continue to work across all projects in your organization. " +
76
- "Check your configured BugSnag project API key.", error);
110
+ "Check your configured BugSnag project API key.");
77
111
  }
78
112
  }
113
+ return true;
79
114
  }
80
115
  getHost(apiKey, subdomain) {
81
116
  if (apiKey?.startsWith(HUB_PREFIX)) {
@@ -126,7 +161,7 @@ export class BugsnagClient {
126
161
  return `${dashboardUrl}/errors/${errorId}${queryString ? `?${queryString}` : ""}`;
127
162
  }
128
163
  async getOrganization() {
129
- let org = this.cache.get(cacheKeys.ORG);
164
+ let org = this.cache?.get(cacheKeys.ORG);
130
165
  if (!org) {
131
166
  const response = await this.currentUserApi.listUserOrganizations();
132
167
  const orgs = response.body;
@@ -134,7 +169,7 @@ export class BugsnagClient {
134
169
  throw new Error("No organizations found for the current user.");
135
170
  }
136
171
  org = orgs[0];
137
- this.cache.set(cacheKeys.ORG, org);
172
+ this.cache?.set(cacheKeys.ORG, org);
138
173
  }
139
174
  return org;
140
175
  }
@@ -143,12 +178,12 @@ export class BugsnagClient {
143
178
  // stores them in the cache for future use.
144
179
  // It throws an error if no organizations are found in the cache.
145
180
  async getProjects() {
146
- let projects = this.cache.get(cacheKeys.PROJECTS);
181
+ let projects = this.cache?.get(cacheKeys.PROJECTS);
147
182
  if (!projects) {
148
183
  const org = await this.getOrganization();
149
184
  const response = await this.currentUserApi.getOrganizationProjects(org.id);
150
185
  projects = response.body;
151
- this.cache.set(cacheKeys.PROJECTS, projects);
186
+ this.cache?.set(cacheKeys.PROJECTS, projects);
152
187
  }
153
188
  return projects;
154
189
  }
@@ -157,28 +192,27 @@ export class BugsnagClient {
157
192
  return projects.find((p) => p.id === projectId) || null;
158
193
  }
159
194
  async getCurrentProject() {
160
- let project = this.cache.get(cacheKeys.CURRENT_PROJECT) ?? null;
195
+ let project = this.cache?.get(cacheKeys.CURRENT_PROJECT) ?? null;
161
196
  if (!project && this.projectApiKey) {
162
197
  const projects = await this.getProjects();
163
198
  project =
164
199
  projects.find((p) => p.apiKey === this.projectApiKey) ?? null;
165
- if (!project) {
166
- throw new ToolError("Unable to find project with the configured API key.");
167
- }
168
- this.cache.set(cacheKeys.CURRENT_PROJECT, project);
169
- if (project) {
170
- this.cache.set(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS, await this.getProjectEventFilters(project));
171
- }
200
+ this.cache?.set(cacheKeys.CURRENT_PROJECT, project);
172
201
  }
173
202
  return project;
174
203
  }
175
204
  async getProjectEventFilters(project) {
176
- let filtersResponse = (await this.projectApi.listProjectEventFields(project.id)).body;
177
- if (!filtersResponse || filtersResponse.length === 0) {
178
- throw new ToolError(`No event fields found for project ${project.name}.`);
205
+ const projectFiltersCache = this.cache?.get(cacheKeys.PROJECT_EVENT_FILTERS) || {};
206
+ if (!projectFiltersCache[project.id]) {
207
+ let filtersResponse = (await this.projectApi.listProjectEventFields(project.id)).body;
208
+ if (!filtersResponse || filtersResponse.length === 0) {
209
+ throw new ToolError(`No event fields found for project ${project.name}.`);
210
+ }
211
+ filtersResponse = filtersResponse.filter((field) => field.displayId && !EXCLUDED_EVENT_FIELDS.has(field.displayId));
212
+ projectFiltersCache[project.id] = filtersResponse;
213
+ this.cache?.set(cacheKeys.PROJECT_EVENT_FILTERS, projectFiltersCache);
179
214
  }
180
- filtersResponse = filtersResponse.filter((field) => field.displayId && !EXCLUDED_EVENT_FIELDS.has(field.displayId));
181
- return filtersResponse;
215
+ return projectFiltersCache[project.id];
182
216
  }
183
217
  async getEvent(eventId, projectId) {
184
218
  const projectIds = projectId
@@ -193,6 +227,10 @@ export class BugsnagClient {
193
227
  if (!maybeProject) {
194
228
  throw new ToolError(`Project with ID ${projectId} not found.`);
195
229
  }
230
+ // If this hasn't been configured at startup, set this to the current project for future tool calls
231
+ if (!this.configuredProjectApiKey) {
232
+ this.cache?.set(cacheKeys.CURRENT_PROJECT, maybeProject);
233
+ }
196
234
  return maybeProject;
197
235
  }
198
236
  else {
@@ -232,72 +270,69 @@ export class BugsnagClient {
232
270
  };
233
271
  }
234
272
  registerTools(register, getInput) {
235
- if (!this.projectApiKey) {
236
- register({
237
- title: "List Projects",
238
- summary: "List all projects in the organization with optional pagination",
239
- purpose: "Retrieve available projects for browsing and selecting which project to analyze",
240
- useCases: [
241
- "Browse available projects when no specific project API key is configured",
242
- "Find project IDs needed for other tools",
243
- "Get an overview of all projects in the organization",
244
- ],
245
- parameters: [
246
- {
247
- name: "pageSize",
248
- type: z.number(),
249
- description: "Number of projects to return per page for pagination",
250
- required: false,
251
- examples: ["10", "25", "50"],
252
- },
253
- {
254
- name: "page",
255
- type: z.number(),
256
- description: "Page number to return (starts from 1)",
257
- required: false,
258
- examples: ["1", "2", "3"],
259
- },
260
- ],
261
- examples: [
262
- {
263
- description: "Get first 10 projects",
264
- parameters: {
265
- pageSize: 10,
266
- page: 1,
267
- },
268
- expectedOutput: "JSON array of project objects with IDs, names, and metadata",
269
- },
270
- {
271
- description: "Get all projects (no pagination)",
272
- parameters: {},
273
- expectedOutput: "JSON array of all available projects",
274
- },
275
- ],
276
- hints: [
277
- "Use pagination for organizations with many projects to avoid large responses",
278
- "Project IDs from this list can be used with other tools when no project API key is configured",
279
- ],
280
- }, async (args, _extra) => {
281
- let projects = await this.getProjects();
282
- if (!projects || projects.length === 0) {
283
- return {
284
- content: [{ type: "text", text: "No projects found." }],
285
- };
286
- }
287
- if (args.pageSize || args.page) {
288
- const pageSize = args.pageSize || 10;
289
- const page = args.page || 1;
290
- projects = projects.slice((page - 1) * pageSize, page * pageSize);
291
- }
292
- const result = {
293
- data: projects,
294
- count: projects.length,
295
- };
296
- return {
297
- content: [{ type: "text", text: JSON.stringify(result) }],
298
- };
299
- });
300
- }
273
+ register({
274
+ title: "Get Current Project",
275
+ summary: "Retrieve the 'current' project on which tools should operate by default. This allows BugSnag tools to be called with no projectId parameter.",
276
+ purpose: "Gets information about the 'current' BugSnag project, including ID and API key",
277
+ useCases: ["Understand if a current project has been set"],
278
+ inputSchema: toolInputParameters.empty,
279
+ hints: [
280
+ "If a project is returned, it can be assumed that the user expects interactions with BugSnag tools to refer to this project",
281
+ "If this tool returns no current project then other BugSnag tools will require an explicit project ID parameter",
282
+ "Call the List Projects tool to see all projects that the user has access to. Get the project ID from this list either by asking the user for the project name or slug",
283
+ "You might find a BugSnag API key in the user's code where they configure the BugSnag SDK that can be matched to a project 'apiKey' field from the project list",
284
+ ],
285
+ }, async (_args, _extra) => {
286
+ const project = await this.getCurrentProject();
287
+ if (!project) {
288
+ throw new ToolError("No current project is configured in the MCP server - use List Projects to see the available projects and use the project ID as a parameter to other BugSnag tools. You can ask the user to select the project based on the name or slug, or use the apiKey field and see if there's a BugSnag API key set in the user's code when they configure the BugSnag SDK");
289
+ }
290
+ return {
291
+ content: [{ type: "text", text: JSON.stringify(project) }],
292
+ };
293
+ });
294
+ const listProjectsInputSchema = z.object({
295
+ apiKey: z
296
+ .string()
297
+ .optional()
298
+ .describe("The API key of the BugSnag project, if known."),
299
+ });
300
+ register({
301
+ title: "List Projects",
302
+ summary: "List all projects in the organization that the current user has access to, or find a project matching an API key.",
303
+ purpose: "Retrieve available projects for browsing and selecting which project to analyze.",
304
+ useCases: [
305
+ "Get an overview of all projects in the organization",
306
+ "Locate a project by its API key if known from the user's code",
307
+ ],
308
+ inputSchema: listProjectsInputSchema,
309
+ hints: [
310
+ "Project IDs from this list can be used with other tools when no project API key is configured",
311
+ ],
312
+ }, async (args, _extra) => {
313
+ const params = listProjectsInputSchema.parse(args);
314
+ let projects = await this.getProjects();
315
+ if (!projects || projects.length === 0) {
316
+ throw new ToolError("No BugSnag projects found for the current user.");
317
+ }
318
+ if (params.apiKey) {
319
+ const matchedProject = projects.find((p) => p.apiKey === params.apiKey);
320
+ projects = matchedProject ? [matchedProject] : [];
321
+ }
322
+ const content = {
323
+ data: projects,
324
+ count: projects.length,
325
+ };
326
+ return {
327
+ content: [{ type: "text", text: JSON.stringify(content) }],
328
+ };
329
+ });
330
+ const getErrorInputSchema = z.object({
331
+ projectId: toolInputParameters.projectId,
332
+ errorId: toolInputParameters.errorId.describe("Unique identifier of the error to retrieve"),
333
+ filters: toolInputParameters.filters.describe("Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields. " +
334
+ "Time filters support extended ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)."),
335
+ });
301
336
  register({
302
337
  title: "Get Error",
303
338
  summary: "Get full details on an error, including aggregated and summarized data across all events (occurrences) and details of the latest event (occurrence), such as breadcrumbs, metadata and the stacktrace. Use the filters parameter to narrow down the summaries further.",
@@ -308,42 +343,7 @@ export class BugsnagClient {
308
343
  "Get error details for debugging and root cause analysis",
309
344
  "Retrieve error metadata for incident reports and documentation",
310
345
  ],
311
- parameters: [
312
- {
313
- name: "errorId",
314
- type: z.string(),
315
- required: true,
316
- description: "Unique identifier of the error to retrieve",
317
- examples: ["6863e2af8c857c0a5023b411"],
318
- },
319
- ...(this.projectApiKey
320
- ? []
321
- : [
322
- {
323
- name: "projectId",
324
- type: z.string(),
325
- required: true,
326
- description: "ID of the project containing the error",
327
- },
328
- ]),
329
- {
330
- name: "filters",
331
- type: FilterObjectSchema,
332
- required: false,
333
- description: "Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields",
334
- examples: [
335
- '{"error.status": [{"type": "eq", "value": "open"}]}',
336
- '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
337
- '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
338
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
339
- ],
340
- constraints: [
341
- "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
342
- "ISO 8601 times must be in UTC and use extended format",
343
- "Relative time periods: h (hours), d (days)",
344
- ],
345
- },
346
- ],
346
+ inputSchema: getErrorInputSchema,
347
347
  outputDescription: "JSON object containing: " +
348
348
  " - error_details: Aggregated data about the error, including first and last seen occurrence" +
349
349
  " - latest_event: Detailed information about the most recent occurrence of the error, including stacktrace, breadcrumbs, user and context" +
@@ -365,15 +365,14 @@ export class BugsnagClient {
365
365
  "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",
366
366
  ],
367
367
  }, async (args, _extra) => {
368
- const project = await this.getInputProject(args.projectId);
369
- if (!args.errorId)
370
- throw new ToolError("Both projectId and errorId arguments are required");
371
- const errorDetails = (await this.errorsApi.viewErrorOnProject(project.id, args.errorId)).body;
368
+ const params = getErrorInputSchema.parse(args);
369
+ const project = await this.getInputProject(params.projectId);
370
+ const errorDetails = (await this.errorsApi.viewErrorOnProject(project.id, params.errorId)).body;
372
371
  if (!errorDetails) {
373
- throw new ToolError(`Error with ID ${args.errorId} not found in project ${project.id}.`);
372
+ throw new ToolError(`Error with ID ${params.errorId} not found in project ${project.id}.`);
374
373
  }
375
374
  const filters = {
376
- error: [{ type: "eq", value: args.errorId }],
375
+ error: [{ type: "eq", value: params.errorId }],
377
376
  ...args.filters,
378
377
  };
379
378
  // Get the latest event for this error using the events endpoint with filters
@@ -398,6 +397,11 @@ export class BugsnagClient {
398
397
  content: [{ type: "text", text: JSON.stringify(content) }],
399
398
  };
400
399
  });
400
+ const getEventDetailsInputSchema = z.object({
401
+ link: z
402
+ .string()
403
+ .describe("Full URL to the event details page in the BugSnag dashboard (web interface), containing project slug and event_id parameter."),
404
+ });
401
405
  register({
402
406
  title: "Get Event Details",
403
407
  summary: "Get detailed information about a specific event using its dashboard URL",
@@ -407,20 +411,7 @@ export class BugsnagClient {
407
411
  "Extract event information from shared links or browser URLs",
408
412
  "Quick lookup of event details without needing separate project and event IDs",
409
413
  ],
410
- parameters: [
411
- {
412
- name: "link",
413
- type: z.string(),
414
- description: "Full URL to the event details page in the BugSnag dashboard (web interface)",
415
- required: true,
416
- examples: [
417
- "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000",
418
- ],
419
- constraints: [
420
- "Must be a valid dashboard URL containing project slug and event_id parameter",
421
- ],
422
- },
423
- ],
414
+ inputSchema: getEventDetailsInputSchema,
424
415
  examples: [
425
416
  {
426
417
  description: "Get event details from a dashboard URL",
@@ -435,9 +426,8 @@ export class BugsnagClient {
435
426
  "This is useful when users share BugSnag dashboard URLs and you need to extract the event data",
436
427
  ],
437
428
  }, async (args, _extra) => {
438
- if (!args.link)
439
- throw new ToolError("link argument is required");
440
- const url = new URL(args.link);
429
+ const params = getEventDetailsInputSchema.parse(args);
430
+ const url = new URL(params.link);
441
431
  const eventId = url.searchParams.get("event_id");
442
432
  const projectSlug = url.pathname.split("/")[2];
443
433
  if (!projectSlug || !eventId)
@@ -453,6 +443,15 @@ export class BugsnagClient {
453
443
  content: [{ type: "text", text: JSON.stringify(response) }],
454
444
  };
455
445
  });
446
+ const listProjectErrorsInputSchema = z.object({
447
+ projectId: toolInputParameters.projectId,
448
+ filters: toolInputParameters.filters.describe("Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields. " +
449
+ "Time filters support extended ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)."),
450
+ sort: toolInputParameters.sort,
451
+ direction: toolInputParameters.direction,
452
+ perPage: toolInputParameters.perPage,
453
+ nextUrl: toolInputParameters.nextUrl,
454
+ });
456
455
  register({
457
456
  title: "List Project Errors",
458
457
  summary: "List and search errors in a project using customizable filters and pagination",
@@ -463,73 +462,7 @@ export class BugsnagClient {
463
462
  "Monitor error trends over time using date range filters",
464
463
  "Find errors affecting specific users or environments using metadata filters",
465
464
  ],
466
- parameters: [
467
- {
468
- name: "filters",
469
- type: FilterObjectSchema.default({
470
- "event.since": [{ type: "eq", value: "30d" }],
471
- "error.status": [{ type: "eq", value: "open" }],
472
- }),
473
- description: "Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields",
474
- required: false,
475
- examples: [
476
- '{"error.status": [{"type": "eq", "value": "open"}]}',
477
- '{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
478
- '{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
479
- '{"user.email": [{"type": "eq", "value": "user@example.com"}]}',
480
- ],
481
- constraints: [
482
- "Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
483
- "ISO 8601 times must be in UTC and use extended format",
484
- "Relative time periods: h (hours), d (days)",
485
- ],
486
- },
487
- {
488
- name: "sort",
489
- type: z
490
- .enum(["first_seen", "last_seen", "events", "users", "unsorted"])
491
- .default("last_seen"),
492
- description: "Field to sort the errors by",
493
- required: false,
494
- examples: ["last_seen"],
495
- },
496
- {
497
- name: "direction",
498
- type: z.enum(["asc", "desc"]).default("desc"),
499
- description: "Sort direction for ordering results",
500
- required: false,
501
- examples: ["desc"],
502
- },
503
- {
504
- name: "perPage",
505
- type: z.number().min(1).max(100).default(30),
506
- description: "How many results to return per page.",
507
- required: false,
508
- examples: ["30", "50", "100"],
509
- },
510
- {
511
- name: "nextUrl",
512
- type: z.string(),
513
- 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.",
514
- required: false,
515
- examples: [
516
- "https://api.bugsnag.com/projects/515fb9337c1074f6fd000003/errors?offset=30&per_page=30&sort=last_seen",
517
- ],
518
- constraints: [
519
- "Only values provided in the output from this tool can be used. Do not attempt to construct it manually.",
520
- ],
521
- },
522
- ...(this.projectApiKey
523
- ? []
524
- : [
525
- {
526
- name: "projectId",
527
- type: z.string(),
528
- description: "ID of the project to query for errors",
529
- required: true,
530
- },
531
- ]),
532
- ],
465
+ inputSchema: listProjectErrorsInputSchema,
533
466
  examples: [
534
467
  {
535
468
  description: "Find errors affecting a specific user in the last 24 hours",
@@ -564,7 +497,7 @@ export class BugsnagClient {
564
497
  },
565
498
  ],
566
499
  hints: [
567
- "Use list_project_event_filters tool first to discover valid filter field names for your project",
500
+ "Use List Project Event Filters tool first to discover valid filter field names for your project",
568
501
  "Combine multiple filters to narrow results - filters are applied with AND logic",
569
502
  "For time filters: use relative format (7d, 24h) for recent periods or ISO 8601 UTC format (2018-05-20T00:00:00Z) for specific dates",
570
503
  "Common time filters: event.since (from this time), event.before (until this time)",
@@ -575,12 +508,13 @@ export class BugsnagClient {
575
508
  "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.",
576
509
  ],
577
510
  }, async (args, _extra) => {
578
- const project = await this.getInputProject(args.projectId);
511
+ const params = listProjectErrorsInputSchema.parse(args);
512
+ const project = await this.getInputProject(params.projectId);
579
513
  // Validate filter keys against cached event fields
580
- if (args.filters) {
581
- const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
514
+ if (params.filters) {
515
+ const eventFields = await this.getProjectEventFilters(project);
582
516
  const validKeys = new Set(eventFields.map((f) => f.displayId));
583
- for (const key of Object.keys(args.filters)) {
517
+ for (const key of Object.keys(params.filters)) {
584
518
  if (!validKeys.has(key)) {
585
519
  throw new ToolError(`Invalid filter key: ${key}`);
586
520
  }
@@ -589,9 +523,9 @@ export class BugsnagClient {
589
523
  const filters = {
590
524
  "event.since": [{ type: "eq", value: "30d" }],
591
525
  "error.status": [{ type: "eq", value: "open" }],
592
- ...args.filters,
526
+ ...params.filters,
593
527
  };
594
- const response = await this.errorsApi.listProjectErrors(project.id, null, args.sort || "last_seen", args.direction || "desc", args.perPage || 30, filters, args.nextUrl);
528
+ const response = await this.errorsApi.listProjectErrors(project.id, null, params.sort, params.direction, params.perPage, filters, params.nextUrl);
595
529
  const result = {
596
530
  data: response.body,
597
531
  next_url: response.nextUrl ?? undefined,
@@ -602,16 +536,19 @@ export class BugsnagClient {
602
536
  content: [{ type: "text", text: JSON.stringify(result) }],
603
537
  };
604
538
  });
539
+ const listProjectEventFiltersInputSchema = z.object({
540
+ projectId: toolInputParameters.projectId,
541
+ });
605
542
  register({
606
543
  title: "List Project Event Filters",
607
- summary: "Get available event filter fields for the current project",
544
+ summary: "Get available event filter fields for a project",
608
545
  purpose: "Discover valid filter field names and options that can be used with the List Errors or Get Error tools",
609
546
  useCases: [
610
547
  "Discover what filter fields are available before searching for errors",
611
548
  "Find the correct field names for filtering by user, environment, or custom metadata",
612
549
  "Understand filter options and data types for building complex queries",
613
550
  ],
614
- parameters: [],
551
+ inputSchema: listProjectEventFiltersInputSchema,
615
552
  examples: [
616
553
  {
617
554
  description: "Get all available filter fields",
@@ -623,14 +560,20 @@ export class BugsnagClient {
623
560
  "Use this tool before the List Errors or Get Error tools to understand available filters",
624
561
  "Look for display_id field in the response - these are the field names to use in filters",
625
562
  ],
626
- }, async (_args, _extra) => {
627
- const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
628
- if (!projectFields)
629
- throw new ToolError("No event filters found in cache.");
563
+ }, async (args, _extra) => {
564
+ const params = listProjectEventFiltersInputSchema.parse(args);
565
+ const eventFilters = await this.getProjectEventFilters(await this.getInputProject(params.projectId));
630
566
  return {
631
- content: [{ type: "text", text: JSON.stringify(projectFields) }],
567
+ content: [{ type: "text", text: JSON.stringify(eventFilters) }],
632
568
  };
633
569
  });
570
+ const updateErrorInputSchema = z.object({
571
+ projectId: toolInputParameters.projectId,
572
+ errorId: toolInputParameters.errorId,
573
+ operation: z
574
+ .enum(PERMITTED_UPDATE_OPERATIONS)
575
+ .describe("The operation to apply to the error"),
576
+ });
634
577
  register({
635
578
  title: "Update Error",
636
579
  summary: "Update the status of an error",
@@ -640,32 +583,7 @@ export class BugsnagClient {
640
583
  "Discard or un-discard an error",
641
584
  "Update the severity of an error",
642
585
  ],
643
- parameters: [
644
- ...(this.projectApiKey
645
- ? []
646
- : [
647
- {
648
- name: "projectId",
649
- type: z.string(),
650
- description: "ID of the project that contains the error to be updated",
651
- required: true,
652
- },
653
- ]),
654
- {
655
- name: "errorId",
656
- type: z.string(),
657
- description: "ID of the error to update",
658
- required: true,
659
- examples: ["6863e2af8c857c0a5023b411"],
660
- },
661
- {
662
- name: "operation",
663
- type: z.enum(PERMITTED_UPDATE_OPERATIONS),
664
- description: "The operation to apply to the error",
665
- required: true,
666
- examples: ["fix", "open", "ignore", "discard", "undiscard"],
667
- },
668
- ],
586
+ inputSchema: updateErrorInputSchema,
669
587
  examples: [
670
588
  {
671
589
  description: "Mark an error as fixed",
@@ -682,10 +600,10 @@ export class BugsnagClient {
682
600
  readOnly: false,
683
601
  idempotent: false,
684
602
  }, async (args, _extra) => {
685
- const { errorId, operation } = args;
686
- const project = await this.getInputProject(args.projectId);
603
+ const params = updateErrorInputSchema.parse(args);
604
+ const project = await this.getInputProject(params.projectId);
687
605
  let severity;
688
- if (operation === "override_severity") {
606
+ if (params.operation === "override_severity") {
689
607
  // illicit the severity from the user
690
608
  const result = await getInput({
691
609
  message: "Please provide the new severity for the error (e.g. 'info', 'warning', 'error', 'critical')",
@@ -705,8 +623,8 @@ export class BugsnagClient {
705
623
  severity = result.content.severity;
706
624
  }
707
625
  }
708
- const result = await this.errorsApi.updateErrorOnProject(project.id, errorId, {
709
- operation: operation,
626
+ const result = await this.errorsApi.updateErrorOnProject(project.id, params.errorId, {
627
+ operation: Object.values(ErrorUpdateRequest.OperationEnum).find((value) => value === params.operation),
710
628
  severity: severity,
711
629
  });
712
630
  return {
@@ -720,6 +638,16 @@ export class BugsnagClient {
720
638
  ],
721
639
  };
722
640
  });
641
+ const listReleasesInputSchema = z.object({
642
+ projectId: toolInputParameters.projectId,
643
+ releaseStage: toolInputParameters.releaseStage,
644
+ visibleOnly: z
645
+ .boolean()
646
+ .describe("Whether to only include releases that are marked as visible in the dashboard")
647
+ .default(false),
648
+ perPage: toolInputParameters.perPage,
649
+ nextUrl: toolInputParameters.nextUrl,
650
+ });
723
651
  register({
724
652
  title: "List Releases",
725
653
  summary: "List releases for a project",
@@ -728,48 +656,7 @@ export class BugsnagClient {
728
656
  "View recent releases to correlate with error spikes",
729
657
  "Filter releases by stage (e.g. production, staging) for targeted analysis",
730
658
  ],
731
- parameters: [
732
- ...(this.projectApiKey
733
- ? []
734
- : [
735
- {
736
- name: "projectId",
737
- type: z.string(),
738
- description: "ID of the project to list releases for",
739
- required: true,
740
- },
741
- ]),
742
- {
743
- name: "releaseStage",
744
- type: z.string().default("production"),
745
- description: "Filter releases by this stage (e.g. production, staging), defaults to 'production'",
746
- required: false,
747
- examples: ["production", "staging"],
748
- },
749
- {
750
- name: "visibleOnly",
751
- type: z.boolean().default(false),
752
- description: "Whether to only include releases that are marked as visible in the dashboard, defaults to false",
753
- required: false,
754
- examples: ["true", "false"],
755
- },
756
- {
757
- name: "perPage",
758
- type: z.number().min(1).max(100).default(30),
759
- description: "How many results to return per page.",
760
- required: false,
761
- examples: ["30", "50", "100"],
762
- },
763
- {
764
- name: "nextUrl",
765
- type: z.string(),
766
- 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.",
767
- required: false,
768
- examples: [
769
- "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30",
770
- ],
771
- },
772
- ],
659
+ inputSchema: listReleasesInputSchema,
773
660
  examples: [
774
661
  {
775
662
  description: "List production releases for a project",
@@ -800,9 +687,10 @@ export class BugsnagClient {
800
687
  idempotent: true,
801
688
  outputDescription: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
802
689
  }, async (args, _extra) => {
803
- const project = await this.getInputProject(args.projectId);
804
- const response = await this.projectApi.listProjectReleaseGroups(project.id, args.releaseStage || "production", false, // Not top-only
805
- args.visibleOnly || false, args.perPage || 30, args.nextUrl);
690
+ const params = listReleasesInputSchema.parse(args);
691
+ const project = await this.getInputProject(params.projectId);
692
+ const response = await this.projectApi.listProjectReleaseGroups(project.id, params.releaseStage, false, // Not top-only
693
+ params.visibleOnly, params.perPage, params.nextUrl);
806
694
  let releases = [];
807
695
  if (response.body) {
808
696
  releases = response.body.map((r) => this.addStabilityData(r, project));
@@ -821,6 +709,10 @@ export class BugsnagClient {
821
709
  ],
822
710
  };
823
711
  });
712
+ const getReleaseInputSchema = z.object({
713
+ projectId: toolInputParameters.projectId,
714
+ releaseId: toolInputParameters.releaseId,
715
+ });
824
716
  register({
825
717
  title: "Get Release",
826
718
  summary: "Get more details for a specific release by its ID, including source control information and associated builds",
@@ -830,25 +722,7 @@ export class BugsnagClient {
830
722
  "Analyze the stability data and targets for a release",
831
723
  "See the builds that make up the release",
832
724
  ],
833
- parameters: [
834
- ...(this.projectApiKey
835
- ? []
836
- : [
837
- {
838
- name: "projectId",
839
- type: z.string(),
840
- description: "ID of the project containing the release",
841
- required: true,
842
- },
843
- ]),
844
- {
845
- name: "releaseId",
846
- type: z.string(),
847
- description: "ID of the release to retrieve",
848
- required: true,
849
- examples: ["5f8d0d55c9e77c0017a1b2c3"],
850
- },
851
- ],
725
+ inputSchema: getReleaseInputSchema,
852
726
  examples: [
853
727
  {
854
728
  description: "Get details for a specific release",
@@ -863,16 +737,15 @@ export class BugsnagClient {
863
737
  idempotent: true,
864
738
  outputDescription: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
865
739
  }, async (args, _extra) => {
866
- if (!args.releaseId)
867
- throw new ToolError("releaseId argument is required");
868
- const project = await this.getInputProject(args.projectId);
869
- const releaseResponse = await this.projectApi.getReleaseGroup(args.releaseId);
740
+ const params = getReleaseInputSchema.parse(args);
741
+ const project = await this.getInputProject(params.projectId);
742
+ const releaseResponse = await this.projectApi.getReleaseGroup(params.releaseId);
870
743
  if (!releaseResponse.body)
871
- throw new ToolError(`No release for ${args.releaseId} found.`);
744
+ throw new ToolError(`No release for ${params.releaseId} found.`);
872
745
  const release = this.addStabilityData(releaseResponse.body, project);
873
746
  let builds = [];
874
747
  if (releaseResponse.body) {
875
- const buildsResponse = await this.projectApi.listBuildsInRelease(args.releaseId);
748
+ const buildsResponse = await this.projectApi.listBuildsInRelease(params.releaseId);
876
749
  if (buildsResponse.body) {
877
750
  builds = buildsResponse.body.map((b) => this.addStabilityData(b, project));
878
751
  }
@@ -889,6 +762,10 @@ export class BugsnagClient {
889
762
  ],
890
763
  };
891
764
  });
765
+ const getBuildInputSchema = z.object({
766
+ projectId: toolInputParameters.projectId,
767
+ buildId: toolInputParameters.buildId,
768
+ });
892
769
  register({
893
770
  title: "Get Build",
894
771
  summary: "Get more details for a specific build by its ID",
@@ -898,25 +775,7 @@ export class BugsnagClient {
898
775
  "Analyze a specific build to correlate with error spikes or deployments",
899
776
  "See the stability targets for a project and if the build meets them",
900
777
  ],
901
- parameters: [
902
- ...(this.projectApiKey
903
- ? []
904
- : [
905
- {
906
- name: "projectId",
907
- type: z.string(),
908
- description: "ID of the project containing the build",
909
- required: true,
910
- },
911
- ]),
912
- {
913
- name: "buildId",
914
- type: z.string(),
915
- description: "ID of the build to retrieve",
916
- required: true,
917
- examples: ["5f8d0d55c9e77c0017a1b2c3"],
918
- },
919
- ],
778
+ inputSchema: getBuildInputSchema,
920
779
  examples: [
921
780
  {
922
781
  description: "Get details for a specific build",
@@ -931,12 +790,11 @@ export class BugsnagClient {
931
790
  idempotent: true,
932
791
  outputDescription: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
933
792
  }, async (args, _extra) => {
934
- if (!args.buildId)
935
- throw new ToolError("buildId argument is required");
936
- const project = await this.getInputProject(args.projectId);
937
- const response = await this.projectApi.getProjectReleaseById(project.id, args.buildId);
793
+ const params = getBuildInputSchema.parse(args);
794
+ const project = await this.getInputProject(params.projectId);
795
+ const response = await this.projectApi.getProjectReleaseById(project.id, params.buildId);
938
796
  if (!response.body)
939
- throw new ToolError(`No build for ${args.buildId} found.`);
797
+ throw new ToolError(`No build for ${params.buildId} found.`);
940
798
  const build = this.addStabilityData(response.body, project);
941
799
  return {
942
800
  content: [{ type: "text", text: JSON.stringify(build) }],