@smartbear/mcp 0.8.0 → 0.9.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.
- package/dist/api-hub/client/api.js +51 -10
- package/dist/api-hub/client/registry-types.js +8 -0
- package/dist/api-hub/client/tools.js +7 -1
- package/dist/api-hub/client.js +3 -0
- package/dist/bugsnag/client/api/CurrentUser.js +12 -49
- package/dist/bugsnag/client/api/Error.js +29 -142
- package/dist/bugsnag/client/api/Project.js +52 -113
- package/dist/bugsnag/client/api/api.js +3743 -0
- package/dist/bugsnag/client/api/base.js +97 -34
- package/dist/bugsnag/client/api/configuration.js +26 -0
- package/dist/bugsnag/client/api/index.js +2 -0
- package/dist/bugsnag/client/filters.js +28 -0
- package/dist/bugsnag/client.js +100 -151
- package/dist/common/server.js +25 -3
- package/dist/common/types.js +6 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- package/dist/pactflow/client/utils.js +5 -4
- package/dist/pactflow/client.js +10 -9
- package/dist/qmetry/client/api/client-api.js +21 -16
- package/dist/qmetry/client/api/error-handler.js +329 -0
- package/dist/qmetry/client/auto-resolve.js +74 -0
- package/dist/qmetry/client/handlers.js +19 -2
- package/dist/qmetry/client/issues.js +26 -0
- package/dist/qmetry/client/project.js +56 -0
- package/dist/qmetry/client/requirement.js +76 -0
- package/dist/qmetry/client/testcase.js +46 -8
- package/dist/qmetry/client/testsuite.js +117 -0
- package/dist/qmetry/client/tools.js +1455 -4
- package/dist/qmetry/client/utils.js +16 -0
- package/dist/qmetry/client.js +19 -16
- package/dist/qmetry/config/constants.js +14 -0
- package/dist/qmetry/config/rest-endpoints.js +20 -0
- package/dist/qmetry/types/common.js +313 -8
- package/dist/qmetry/types/issues.js +6 -0
- package/dist/qmetry/types/project.js +10 -0
- package/dist/qmetry/types/requirements.js +19 -0
- package/dist/qmetry/types/testcase.js +14 -0
- package/dist/qmetry/types/testsuite.js +26 -0
- package/dist/reflect/client.js +7 -6
- package/dist/zephyr/common/auth-service.js +1 -0
- package/package.json +1 -1
- package/dist/bugsnag/client/api/filters.js +0 -167
- package/dist/bugsnag/client/configuration.js +0 -10
- package/dist/bugsnag/client/index.js +0 -2
package/dist/bugsnag/client.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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 {
|
|
5
|
-
import { ProjectAPI, } from "./client/api/
|
|
6
|
-
import {
|
|
4
|
+
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";
|
|
7
7
|
const HUB_PREFIX = "00000";
|
|
8
8
|
const DEFAULT_DOMAIN = "bugsnag.com";
|
|
9
9
|
const HUB_DOMAIN = "bugsnag.smartbear.com";
|
|
@@ -39,7 +39,7 @@ export class BugsnagClient {
|
|
|
39
39
|
this.apiEndpoint = this.getEndpoint("api", projectApiKey, endpoint);
|
|
40
40
|
this.appEndpoint = this.getEndpoint("app", projectApiKey, endpoint);
|
|
41
41
|
const config = new Configuration({
|
|
42
|
-
|
|
42
|
+
apiKey: `token ${token}`,
|
|
43
43
|
headers: {
|
|
44
44
|
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
|
|
45
45
|
"Content-Type": "application/json",
|
|
@@ -121,15 +121,15 @@ export class BugsnagClient {
|
|
|
121
121
|
async getDashboardUrl(project) {
|
|
122
122
|
return `${this.appEndpoint}/${(await this.getOrganization()).slug}/${project.slug}`;
|
|
123
123
|
}
|
|
124
|
-
async getErrorUrl(project, errorId, queryString
|
|
124
|
+
async getErrorUrl(project, errorId, queryString) {
|
|
125
125
|
const dashboardUrl = await this.getDashboardUrl(project);
|
|
126
|
-
return `${dashboardUrl}/errors/${errorId}${queryString}`;
|
|
126
|
+
return `${dashboardUrl}/errors/${errorId}${queryString ? `?${queryString}` : ""}`;
|
|
127
127
|
}
|
|
128
128
|
async getOrganization() {
|
|
129
129
|
let org = this.cache.get(cacheKeys.ORG);
|
|
130
130
|
if (!org) {
|
|
131
131
|
const response = await this.currentUserApi.listUserOrganizations();
|
|
132
|
-
const orgs = response.body
|
|
132
|
+
const orgs = response.body;
|
|
133
133
|
if (!orgs || orgs.length === 0) {
|
|
134
134
|
throw new Error("No organizations found for the current user.");
|
|
135
135
|
}
|
|
@@ -147,7 +147,7 @@ export class BugsnagClient {
|
|
|
147
147
|
if (!projects) {
|
|
148
148
|
const org = await this.getOrganization();
|
|
149
149
|
const response = await this.currentUserApi.getOrganizationProjects(org.id);
|
|
150
|
-
projects = response.body
|
|
150
|
+
projects = response.body;
|
|
151
151
|
this.cache.set(cacheKeys.PROJECTS, projects);
|
|
152
152
|
}
|
|
153
153
|
return projects;
|
|
@@ -160,9 +160,10 @@ export class BugsnagClient {
|
|
|
160
160
|
let project = this.cache.get(cacheKeys.CURRENT_PROJECT) ?? null;
|
|
161
161
|
if (!project && this.projectApiKey) {
|
|
162
162
|
const projects = await this.getProjects();
|
|
163
|
-
project =
|
|
163
|
+
project =
|
|
164
|
+
projects.find((p) => p.apiKey === this.projectApiKey) ?? null;
|
|
164
165
|
if (!project) {
|
|
165
|
-
throw new
|
|
166
|
+
throw new ToolError("Unable to find project with the configured API key.");
|
|
166
167
|
}
|
|
167
168
|
this.cache.set(cacheKeys.CURRENT_PROJECT, project);
|
|
168
169
|
if (project) {
|
|
@@ -174,9 +175,9 @@ export class BugsnagClient {
|
|
|
174
175
|
async getProjectEventFilters(project) {
|
|
175
176
|
let filtersResponse = (await this.projectApi.listProjectEventFields(project.id)).body;
|
|
176
177
|
if (!filtersResponse || filtersResponse.length === 0) {
|
|
177
|
-
throw new
|
|
178
|
+
throw new ToolError(`No event fields found for project ${project.name}.`);
|
|
178
179
|
}
|
|
179
|
-
filtersResponse = filtersResponse.filter((field) => !EXCLUDED_EVENT_FIELDS.has(field.
|
|
180
|
+
filtersResponse = filtersResponse.filter((field) => field.displayId && !EXCLUDED_EVENT_FIELDS.has(field.displayId));
|
|
180
181
|
return filtersResponse;
|
|
181
182
|
}
|
|
182
183
|
async getEvent(eventId, projectId) {
|
|
@@ -186,111 +187,48 @@ export class BugsnagClient {
|
|
|
186
187
|
const projectEvents = await Promise.all(projectIds.map((projectId) => this.errorsApi.viewEventById(projectId, eventId).catch((_e) => null)));
|
|
187
188
|
return projectEvents.find((event) => event && !!event.body)?.body || null;
|
|
188
189
|
}
|
|
189
|
-
async updateError(projectId, errorId, operation, options) {
|
|
190
|
-
const errorUpdateRequest = {
|
|
191
|
-
operation: operation,
|
|
192
|
-
...options,
|
|
193
|
-
};
|
|
194
|
-
const response = await this.errorsApi.updateErrorOnProject(projectId, errorId, errorUpdateRequest);
|
|
195
|
-
return response.status === 200 || response.status === 204;
|
|
196
|
-
}
|
|
197
190
|
async getInputProject(projectId) {
|
|
198
191
|
if (typeof projectId === "string") {
|
|
199
192
|
const maybeProject = await this.getProject(projectId);
|
|
200
193
|
if (!maybeProject) {
|
|
201
|
-
throw new
|
|
194
|
+
throw new ToolError(`Project with ID ${projectId} not found.`);
|
|
202
195
|
}
|
|
203
196
|
return maybeProject;
|
|
204
197
|
}
|
|
205
198
|
else {
|
|
206
199
|
const currentProject = await this.getCurrentProject();
|
|
207
200
|
if (!currentProject) {
|
|
208
|
-
throw new
|
|
201
|
+
throw new ToolError("No current project found. Please provide a projectId or configure a project API key.");
|
|
209
202
|
}
|
|
210
203
|
return currentProject;
|
|
211
204
|
}
|
|
212
205
|
}
|
|
213
|
-
async listBuilds(projectId, opts) {
|
|
214
|
-
const response = await this.projectApi.listBuilds(projectId, opts);
|
|
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
|
-
};
|
|
225
|
-
}
|
|
226
|
-
async getBuild(projectId, buildId) {
|
|
227
|
-
const response = await this.projectApi.getBuild(projectId, buildId);
|
|
228
|
-
if (!response.body)
|
|
229
|
-
throw new Error(`No build for ${buildId} found.`);
|
|
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) };
|
|
234
|
-
}
|
|
235
|
-
async listReleases(projectId, opts) {
|
|
236
|
-
const response = await this.projectApi.listReleases(projectId, opts);
|
|
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
|
-
};
|
|
247
|
-
}
|
|
248
|
-
async getRelease(projectId, releaseId) {
|
|
249
|
-
const response = await this.projectApi.getRelease(releaseId);
|
|
250
|
-
if (!response.body)
|
|
251
|
-
throw new Error(`No release for ${releaseId} found.`);
|
|
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) };
|
|
256
|
-
}
|
|
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
|
-
};
|
|
269
|
-
}
|
|
270
206
|
addStabilityData(source, project) {
|
|
271
|
-
const
|
|
207
|
+
const accumulativeDailyUsersSeen = source.accumulativeDailyUsersSeen || 0;
|
|
208
|
+
const accumulativeDailyUsersWithUnhandled = source.accumulativeDailyUsersWithUnhandled || 0;
|
|
209
|
+
const userStability = accumulativeDailyUsersSeen === 0 // avoid division by zero
|
|
272
210
|
? 0
|
|
273
|
-
: (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
211
|
+
: (accumulativeDailyUsersSeen - accumulativeDailyUsersWithUnhandled) /
|
|
212
|
+
accumulativeDailyUsersSeen;
|
|
213
|
+
const totalSessionsCount = source.totalSessionsCount || 0;
|
|
214
|
+
const unhandledSessionsCount = source.unhandledSessionsCount || 0;
|
|
215
|
+
const sessionStability = totalSessionsCount === 0 // avoid division by zero
|
|
277
216
|
? 0
|
|
278
|
-
: (
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
const meets_critical_stability = stabilityMetric >= project.critical_stability.value;
|
|
217
|
+
: (totalSessionsCount - unhandledSessionsCount) / totalSessionsCount;
|
|
218
|
+
const stabilityMetric = project.stabilityTargetType === "user" ? userStability : sessionStability;
|
|
219
|
+
const targetStability = project.targetStability?.value || 0;
|
|
220
|
+
const criticalStability = project.criticalStability?.value || 0;
|
|
221
|
+
const meetsTargetStability = stabilityMetric >= targetStability;
|
|
222
|
+
const meetsCriticalStability = stabilityMetric >= criticalStability;
|
|
285
223
|
return {
|
|
286
224
|
...source,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
225
|
+
userStability,
|
|
226
|
+
sessionStability,
|
|
227
|
+
stabilityTargetType: project.stabilityTargetType || "user",
|
|
228
|
+
targetStability,
|
|
229
|
+
criticalStability,
|
|
230
|
+
meetsTargetStability,
|
|
231
|
+
meetsCriticalStability,
|
|
294
232
|
};
|
|
295
233
|
}
|
|
296
234
|
registerTools(register, getInput) {
|
|
@@ -429,28 +367,22 @@ export class BugsnagClient {
|
|
|
429
367
|
}, async (args, _extra) => {
|
|
430
368
|
const project = await this.getInputProject(args.projectId);
|
|
431
369
|
if (!args.errorId)
|
|
432
|
-
throw new
|
|
370
|
+
throw new ToolError("Both projectId and errorId arguments are required");
|
|
433
371
|
const errorDetails = (await this.errorsApi.viewErrorOnProject(project.id, args.errorId)).body;
|
|
434
372
|
if (!errorDetails) {
|
|
435
|
-
throw new
|
|
373
|
+
throw new ToolError(`Error with ID ${args.errorId} not found in project ${project.id}.`);
|
|
436
374
|
}
|
|
437
|
-
// Build query parameters
|
|
438
|
-
const params = new URLSearchParams();
|
|
439
|
-
// Add sorting and pagination parameters to get the latest event
|
|
440
|
-
params.append("sort", "timestamp");
|
|
441
|
-
params.append("direction", "desc");
|
|
442
|
-
params.append("per_page", "1");
|
|
443
|
-
params.append("full_reports", "true");
|
|
444
375
|
const filters = {
|
|
445
376
|
error: [{ type: "eq", value: args.errorId }],
|
|
446
377
|
...args.filters,
|
|
447
378
|
};
|
|
448
|
-
const filtersQueryString = toQueryString(filters);
|
|
449
|
-
const listEventsQueryString = `?${params}&${filtersQueryString}`;
|
|
450
379
|
// Get the latest event for this error using the events endpoint with filters
|
|
451
380
|
let latestEvent = null;
|
|
452
381
|
try {
|
|
453
|
-
|
|
382
|
+
const latestEvents = (await this.errorsApi.listEventsOnProject(project.id, null, "timestamp", "desc", 1, filters, true)).body;
|
|
383
|
+
if (latestEvents && latestEvents.length > 0) {
|
|
384
|
+
latestEvent = latestEvents[0];
|
|
385
|
+
}
|
|
454
386
|
}
|
|
455
387
|
catch (e) {
|
|
456
388
|
console.warn("Failed to fetch latest event:", e);
|
|
@@ -459,9 +391,8 @@ export class BugsnagClient {
|
|
|
459
391
|
const content = {
|
|
460
392
|
error_details: errorDetails,
|
|
461
393
|
latest_event: latestEvent,
|
|
462
|
-
pivots: (await this.errorsApi.
|
|
463
|
-
|
|
464
|
-
url: await this.getErrorUrl(project, args.errorId, `?${filtersQueryString}`),
|
|
394
|
+
pivots: (await this.errorsApi.getPivotValuesOnAnError(project.id, args.errorId, filters, 5)).body || [],
|
|
395
|
+
url: await this.getErrorUrl(project, args.errorId, toUrlSearchParams(filters).toString()),
|
|
465
396
|
};
|
|
466
397
|
return {
|
|
467
398
|
content: [{ type: "text", text: JSON.stringify(content) }],
|
|
@@ -505,17 +436,17 @@ export class BugsnagClient {
|
|
|
505
436
|
],
|
|
506
437
|
}, async (args, _extra) => {
|
|
507
438
|
if (!args.link)
|
|
508
|
-
throw new
|
|
439
|
+
throw new ToolError("link argument is required");
|
|
509
440
|
const url = new URL(args.link);
|
|
510
441
|
const eventId = url.searchParams.get("event_id");
|
|
511
442
|
const projectSlug = url.pathname.split("/")[2];
|
|
512
443
|
if (!projectSlug || !eventId)
|
|
513
|
-
throw new
|
|
444
|
+
throw new ToolError("Both projectSlug and eventId must be present in the link");
|
|
514
445
|
// get the project id from list of projects
|
|
515
446
|
const projects = await this.getProjects();
|
|
516
447
|
const projectId = projects.find((p) => p.slug === projectSlug)?.id;
|
|
517
448
|
if (!projectId) {
|
|
518
|
-
throw new
|
|
449
|
+
throw new ToolError("Project with the specified slug not found.");
|
|
519
450
|
}
|
|
520
451
|
const response = await this.getEvent(eventId, projectId);
|
|
521
452
|
return {
|
|
@@ -578,7 +509,7 @@ export class BugsnagClient {
|
|
|
578
509
|
},
|
|
579
510
|
{
|
|
580
511
|
name: "nextUrl",
|
|
581
|
-
type: z.string()
|
|
512
|
+
type: z.string(),
|
|
582
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.",
|
|
583
514
|
required: false,
|
|
584
515
|
examples: [
|
|
@@ -648,29 +579,19 @@ export class BugsnagClient {
|
|
|
648
579
|
// Validate filter keys against cached event fields
|
|
649
580
|
if (args.filters) {
|
|
650
581
|
const eventFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS) || [];
|
|
651
|
-
const validKeys = new Set(eventFields.map((f) => f.
|
|
582
|
+
const validKeys = new Set(eventFields.map((f) => f.displayId));
|
|
652
583
|
for (const key of Object.keys(args.filters)) {
|
|
653
584
|
if (!validKeys.has(key)) {
|
|
654
|
-
throw new
|
|
585
|
+
throw new ToolError(`Invalid filter key: ${key}`);
|
|
655
586
|
}
|
|
656
587
|
}
|
|
657
588
|
}
|
|
658
|
-
const
|
|
589
|
+
const filters = {
|
|
659
590
|
"event.since": [{ type: "eq", value: "30d" }],
|
|
660
591
|
"error.status": [{ type: "eq", value: "open" }],
|
|
592
|
+
...args.filters,
|
|
661
593
|
};
|
|
662
|
-
const
|
|
663
|
-
filters: { ...defaultFilters, ...args.filters },
|
|
664
|
-
};
|
|
665
|
-
if (args.sort !== undefined)
|
|
666
|
-
options.sort = args.sort;
|
|
667
|
-
if (args.direction !== undefined)
|
|
668
|
-
options.direction = args.direction;
|
|
669
|
-
if (args.perPage !== undefined)
|
|
670
|
-
options.per_page = args.perPage;
|
|
671
|
-
if (args.nextUrl !== undefined)
|
|
672
|
-
options.next_url = args.nextUrl;
|
|
673
|
-
const response = await this.errorsApi.listProjectErrors(project.id, options);
|
|
594
|
+
const response = await this.errorsApi.listProjectErrors(project.id, null, args.sort || "last_seen", args.direction || "desc", args.perPage || 30, filters, args.nextUrl);
|
|
674
595
|
const result = {
|
|
675
596
|
data: response.body,
|
|
676
597
|
next_url: response.nextUrl ?? undefined,
|
|
@@ -705,7 +626,7 @@ export class BugsnagClient {
|
|
|
705
626
|
}, async (_args, _extra) => {
|
|
706
627
|
const projectFields = this.cache.get(cacheKeys.CURRENT_PROJECT_EVENT_FILTERS);
|
|
707
628
|
if (!projectFields)
|
|
708
|
-
throw new
|
|
629
|
+
throw new ToolError("No event filters found in cache.");
|
|
709
630
|
return {
|
|
710
631
|
content: [{ type: "text", text: JSON.stringify(projectFields) }],
|
|
711
632
|
};
|
|
@@ -784,12 +705,18 @@ export class BugsnagClient {
|
|
|
784
705
|
severity = result.content.severity;
|
|
785
706
|
}
|
|
786
707
|
}
|
|
787
|
-
const result = await this.
|
|
788
|
-
|
|
708
|
+
const result = await this.errorsApi.updateErrorOnProject(project.id, errorId, {
|
|
709
|
+
operation: operation,
|
|
710
|
+
severity: severity,
|
|
789
711
|
});
|
|
790
712
|
return {
|
|
791
713
|
content: [
|
|
792
|
-
{
|
|
714
|
+
{
|
|
715
|
+
type: "text",
|
|
716
|
+
text: JSON.stringify({
|
|
717
|
+
success: result.status === 200 || result.status === 204,
|
|
718
|
+
}),
|
|
719
|
+
},
|
|
793
720
|
],
|
|
794
721
|
};
|
|
795
722
|
});
|
|
@@ -826,6 +753,13 @@ export class BugsnagClient {
|
|
|
826
753
|
required: false,
|
|
827
754
|
examples: ["true", "false"],
|
|
828
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
|
+
},
|
|
829
763
|
{
|
|
830
764
|
name: "nextUrl",
|
|
831
765
|
type: z.string(),
|
|
@@ -866,19 +800,21 @@ export class BugsnagClient {
|
|
|
866
800
|
idempotent: true,
|
|
867
801
|
outputFormat: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available",
|
|
868
802
|
}, async (args, _extra) => {
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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);
|
|
806
|
+
let releases = [];
|
|
807
|
+
if (response.body) {
|
|
808
|
+
releases = response.body.map((r) => this.addStabilityData(r, project));
|
|
809
|
+
}
|
|
874
810
|
return {
|
|
875
811
|
content: [
|
|
876
812
|
{
|
|
877
813
|
type: "text",
|
|
878
814
|
text: JSON.stringify({
|
|
879
|
-
data:
|
|
815
|
+
data: releases,
|
|
880
816
|
next_url: response.nextUrl ?? undefined,
|
|
881
|
-
data_count:
|
|
817
|
+
data_count: releases.length,
|
|
882
818
|
total_count: response.totalCount ?? undefined,
|
|
883
819
|
}),
|
|
884
820
|
},
|
|
@@ -928,17 +864,26 @@ export class BugsnagClient {
|
|
|
928
864
|
outputFormat: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets",
|
|
929
865
|
}, async (args, _extra) => {
|
|
930
866
|
if (!args.releaseId)
|
|
931
|
-
throw new
|
|
867
|
+
throw new ToolError("releaseId argument is required");
|
|
932
868
|
const project = await this.getInputProject(args.projectId);
|
|
933
|
-
const releaseResponse = await this.
|
|
934
|
-
|
|
869
|
+
const releaseResponse = await this.projectApi.getReleaseGroup(args.releaseId);
|
|
870
|
+
if (!releaseResponse.body)
|
|
871
|
+
throw new ToolError(`No release for ${args.releaseId} found.`);
|
|
872
|
+
const release = this.addStabilityData(releaseResponse.body, project);
|
|
873
|
+
let builds = [];
|
|
874
|
+
if (releaseResponse.body) {
|
|
875
|
+
const buildsResponse = await this.projectApi.listBuildsInRelease(args.releaseId);
|
|
876
|
+
if (buildsResponse.body) {
|
|
877
|
+
builds = buildsResponse.body.map((b) => this.addStabilityData(b, project));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
935
880
|
return {
|
|
936
881
|
content: [
|
|
937
882
|
{
|
|
938
883
|
type: "text",
|
|
939
884
|
text: JSON.stringify({
|
|
940
|
-
release:
|
|
941
|
-
builds:
|
|
885
|
+
release: release,
|
|
886
|
+
builds: builds,
|
|
942
887
|
}),
|
|
943
888
|
},
|
|
944
889
|
],
|
|
@@ -987,10 +932,14 @@ export class BugsnagClient {
|
|
|
987
932
|
outputFormat: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets",
|
|
988
933
|
}, async (args, _extra) => {
|
|
989
934
|
if (!args.buildId)
|
|
990
|
-
throw new
|
|
991
|
-
const
|
|
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);
|
|
938
|
+
if (!response.body)
|
|
939
|
+
throw new ToolError(`No build for ${args.buildId} found.`);
|
|
940
|
+
const build = this.addStabilityData(response.body, project);
|
|
992
941
|
return {
|
|
993
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
942
|
+
content: [{ type: "text", text: JSON.stringify(build) }],
|
|
994
943
|
};
|
|
995
944
|
});
|
|
996
945
|
}
|
package/dist/common/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/m
|
|
|
2
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
|
+
import { ToolError } from "./types.js";
|
|
5
6
|
export class SmartBearMcpServer extends McpServer {
|
|
6
7
|
constructor() {
|
|
7
8
|
super({
|
|
@@ -32,7 +33,24 @@ export class SmartBearMcpServer extends McpServer {
|
|
|
32
33
|
return await cb(args, extra);
|
|
33
34
|
}
|
|
34
35
|
catch (e) {
|
|
35
|
-
|
|
36
|
+
// ToolErrors should not be reported to BugSnag
|
|
37
|
+
if (e instanceof ToolError) {
|
|
38
|
+
return {
|
|
39
|
+
isError: true,
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `Error executing ${toolTitle}: ${e.message}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
Bugsnag.notify(e, (event) => {
|
|
50
|
+
event.addMetadata("app", { tool: toolName });
|
|
51
|
+
event.unhandled = true;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
36
54
|
throw e;
|
|
37
55
|
}
|
|
38
56
|
});
|
|
@@ -41,14 +59,18 @@ export class SmartBearMcpServer extends McpServer {
|
|
|
41
59
|
});
|
|
42
60
|
if (client.registerResources) {
|
|
43
61
|
client.registerResources((name, path, cb) => {
|
|
44
|
-
|
|
62
|
+
const url = `${client.prefix}://${name}/${path}`;
|
|
63
|
+
return super.registerResource(name, new ResourceTemplate(url, {
|
|
45
64
|
list: undefined,
|
|
46
65
|
}), {}, async (url, variables, extra) => {
|
|
47
66
|
try {
|
|
48
67
|
return await cb(url, variables, extra);
|
|
49
68
|
}
|
|
50
69
|
catch (e) {
|
|
51
|
-
Bugsnag.notify(e)
|
|
70
|
+
Bugsnag.notify(e, (event) => {
|
|
71
|
+
event.addMetadata("app", { resource: name, url: url });
|
|
72
|
+
event.unhandled = true;
|
|
73
|
+
});
|
|
52
74
|
throw e;
|
|
53
75
|
}
|
|
54
76
|
});
|
package/dist/common/types.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolError } from "../../common/types.js";
|
|
1
2
|
import { EndpointMatcherSchema, MatcherRecommendationInputSchema, } from "./ai.js";
|
|
2
3
|
import { OADMatcherPrompt } from "./prompts.js";
|
|
3
4
|
/**
|
|
@@ -30,7 +31,7 @@ export async function getOADMatcherRecommendations(openAPI, server) {
|
|
|
30
31
|
return matcherRecommendations;
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
|
-
throw new
|
|
34
|
+
throw new ToolError("Unable to parse recommendations please provide OpenAPI matchers manually.");
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import yaml from "js-yaml";
|
|
2
2
|
// @ts-expect-error missing type declarations
|
|
3
3
|
import Swagger from "swagger-client";
|
|
4
|
+
import { ToolError } from "../../common/types.js";
|
|
4
5
|
import { RemoteOpenAPIDocumentSchema, } from "./ai.js";
|
|
5
6
|
/**
|
|
6
7
|
* Resolve the OpenAPI specification from the provided input.
|
|
@@ -12,12 +13,12 @@ import { RemoteOpenAPIDocumentSchema, } from "./ai.js";
|
|
|
12
13
|
export async function resolveOpenAPISpec(remoteOpenAPIDocument) {
|
|
13
14
|
const openAPISchema = RemoteOpenAPIDocumentSchema.safeParse(remoteOpenAPIDocument);
|
|
14
15
|
if (openAPISchema.error || !remoteOpenAPIDocument) {
|
|
15
|
-
throw new
|
|
16
|
+
throw new ToolError(`Invalid RemoteOpenAPIDocument: ${JSON.stringify(openAPISchema.error?.issues)}`);
|
|
16
17
|
}
|
|
17
18
|
const unresolvedSpec = await getRemoteSpecContents(openAPISchema.data);
|
|
18
19
|
const resolvedSpec = await Swagger.resolve({ spec: unresolvedSpec });
|
|
19
20
|
if (resolvedSpec.errors?.length) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new ToolError(`Failed to resolve OpenAPI document: ${resolvedSpec.errors?.join(", ")}`);
|
|
21
22
|
}
|
|
22
23
|
return resolvedSpec.spec;
|
|
23
24
|
}
|
|
@@ -30,7 +31,7 @@ export async function resolveOpenAPISpec(remoteOpenAPIDocument) {
|
|
|
30
31
|
*/
|
|
31
32
|
export async function getRemoteSpecContents(openAPISchema) {
|
|
32
33
|
if (!openAPISchema.url) {
|
|
33
|
-
throw new
|
|
34
|
+
throw new ToolError("'url' must be provided.");
|
|
34
35
|
}
|
|
35
36
|
let headers = {};
|
|
36
37
|
if (openAPISchema.authToken) {
|
|
@@ -51,7 +52,7 @@ export async function getRemoteSpecContents(openAPISchema) {
|
|
|
51
52
|
return yaml.load(specRawBody);
|
|
52
53
|
}
|
|
53
54
|
catch (yamlError) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new ToolError(`Unsupported Content-Type: ${remoteSpec.headers.get("Content-Type")} for remote OpenAPI document. Found following parse errors:-\nJSON parse error: ${jsonError}\nYAML parse error: ${yamlError}`);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
package/dist/pactflow/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
2
|
+
import { ToolError, } from "../common/types.js";
|
|
2
3
|
import { getOADMatcherRecommendations, getUserMatcherSelection, } from "./client/prompt-utils.js";
|
|
3
4
|
import { PROMPTS } from "./client/prompts.js";
|
|
4
5
|
import { TOOLS } from "./client/tools.js";
|
|
@@ -65,7 +66,7 @@ export class PactflowClient {
|
|
|
65
66
|
body: JSON.stringify(toolInput),
|
|
66
67
|
});
|
|
67
68
|
if (!response.ok) {
|
|
68
|
-
throw new
|
|
69
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
69
70
|
}
|
|
70
71
|
const status_response = await response.json();
|
|
71
72
|
return await this.pollForCompletion(status_response, "Generation");
|
|
@@ -93,7 +94,7 @@ export class PactflowClient {
|
|
|
93
94
|
body: JSON.stringify(toolInput),
|
|
94
95
|
});
|
|
95
96
|
if (!response.ok) {
|
|
96
|
-
throw new
|
|
97
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
97
98
|
}
|
|
98
99
|
const status_response = await response.json();
|
|
99
100
|
return await this.pollForCompletion(status_response, "Review Pacts");
|
|
@@ -116,7 +117,7 @@ export class PactflowClient {
|
|
|
116
117
|
});
|
|
117
118
|
if (!response.ok) {
|
|
118
119
|
const errorText = await response.text().catch(() => "");
|
|
119
|
-
throw new
|
|
120
|
+
throw new ToolError(`PactFlow AI Entitlements Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
120
121
|
}
|
|
121
122
|
return (await response.json());
|
|
122
123
|
}
|
|
@@ -145,7 +146,7 @@ export class PactflowClient {
|
|
|
145
146
|
});
|
|
146
147
|
// Check if the response is OK (status 200)
|
|
147
148
|
if (!response.ok) {
|
|
148
|
-
throw new
|
|
149
|
+
throw new ToolError(`HTTP error! status: ${response.status}`);
|
|
149
150
|
}
|
|
150
151
|
return response.json();
|
|
151
152
|
}
|
|
@@ -161,12 +162,12 @@ export class PactflowClient {
|
|
|
161
162
|
return await this.getResult(status_response.result_url);
|
|
162
163
|
}
|
|
163
164
|
if (statusCheck.status !== 202) {
|
|
164
|
-
throw new
|
|
165
|
+
throw new ToolError(`${operationName} failed with status: ${statusCheck.status}`);
|
|
165
166
|
}
|
|
166
167
|
// Wait before next poll
|
|
167
168
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
168
169
|
}
|
|
169
|
-
throw new
|
|
170
|
+
throw new ToolError(`${operationName} timed out after ${timeout / 1000} seconds`);
|
|
170
171
|
}
|
|
171
172
|
// PactFlow / Pact_Broker client methods
|
|
172
173
|
async getProviderStates({ provider, }) {
|
|
@@ -176,7 +177,7 @@ export class PactflowClient {
|
|
|
176
177
|
headers: this.headers,
|
|
177
178
|
});
|
|
178
179
|
if (!response.ok) {
|
|
179
|
-
throw new
|
|
180
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
180
181
|
}
|
|
181
182
|
return response.json();
|
|
182
183
|
}
|
|
@@ -206,7 +207,7 @@ export class PactflowClient {
|
|
|
206
207
|
});
|
|
207
208
|
if (!response.ok) {
|
|
208
209
|
const errorText = await response.text().catch(() => "");
|
|
209
|
-
throw new
|
|
210
|
+
throw new ToolError(`Can-I-Deploy Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
210
211
|
}
|
|
211
212
|
return (await response.json());
|
|
212
213
|
}
|
|
@@ -265,7 +266,7 @@ export class PactflowClient {
|
|
|
265
266
|
});
|
|
266
267
|
if (!response.ok) {
|
|
267
268
|
const errorText = await response.text().catch(() => "");
|
|
268
|
-
throw new
|
|
269
|
+
throw new ToolError(`Matrix Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
269
270
|
}
|
|
270
271
|
return (await response.json());
|
|
271
272
|
}
|