@smartbear/mcp 0.7.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/README.md +19 -2
- package/dist/api-hub/client/api.js +198 -23
- package/dist/api-hub/client/registry-types.js +22 -0
- package/dist/api-hub/client/tools.js +19 -1
- package/dist/api-hub/client.js +9 -0
- package/dist/bugsnag/client/api/CurrentUser.js +13 -50
- package/dist/bugsnag/client/api/Error.js +30 -155
- package/dist/bugsnag/client/api/Project.js +56 -124
- package/dist/bugsnag/client/api/api.js +3743 -0
- package/dist/bugsnag/client/api/base.js +107 -32
- 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 +157 -325
- package/dist/common/server.js +29 -4
- package/dist/common/types.js +6 -1
- package/dist/index.js +8 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- package/dist/pactflow/client/tools.js +9 -9
- package/dist/pactflow/client/utils.js +5 -4
- package/dist/pactflow/client.js +16 -14
- 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/client.js +16 -0
- package/dist/zephyr/common/api-client.js +27 -0
- package/dist/zephyr/common/auth-service.js +15 -0
- package/dist/zephyr/common/types.js +35 -0
- package/dist/zephyr/tool/project/get-projects.js +54 -0
- package/dist/zephyr/tool/zephyr-tool.js +1 -0
- package/package.json +3 -2
- 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/common/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodString, ZodUnion, } from "zod";
|
|
2
|
+
import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodOptional, ZodString, ZodUnion, } from "zod";
|
|
3
3
|
import Bugsnag from "../common/bugsnag.js";
|
|
4
4
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
|
|
5
|
+
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
|
});
|
|
@@ -142,6 +164,9 @@ export class SmartBearMcpServer extends McpServer {
|
|
|
142
164
|
`${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join("\n - ")}` : ""}`);
|
|
143
165
|
}
|
|
144
166
|
getReadableTypeName(zodType) {
|
|
167
|
+
if (zodType instanceof ZodOptional) {
|
|
168
|
+
zodType = zodType._def.innerType;
|
|
169
|
+
}
|
|
145
170
|
if (zodType instanceof ZodString)
|
|
146
171
|
return "string";
|
|
147
172
|
if (zodType instanceof ZodNumber)
|
package/dist/common/types.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { SmartBearMcpServer } from "./common/server.js";
|
|
|
7
7
|
import { PactflowClient } from "./pactflow/client.js";
|
|
8
8
|
import { QmetryClient } from "./qmetry/client.js";
|
|
9
9
|
import { ReflectClient } from "./reflect/client.js";
|
|
10
|
+
import { ZephyrClient } from "./zephyr/client.js";
|
|
10
11
|
// This is used to report errors in the MCP server itself
|
|
11
12
|
// If you want to use your own BugSnag API key, set the MCP_SERVER_BUGSNAG_API_KEY environment variable
|
|
12
13
|
const McpServerBugsnagAPIKey = process.env.MCP_SERVER_BUGSNAG_API_KEY;
|
|
@@ -24,6 +25,8 @@ async function main() {
|
|
|
24
25
|
const pactBrokerPassword = process.env.PACT_BROKER_PASSWORD;
|
|
25
26
|
const qmetryToken = process.env.QMETRY_API_KEY;
|
|
26
27
|
const qmetryBaseUrl = process.env.QMETRY_BASE_URL;
|
|
28
|
+
const zephyrToken = process.env.ZEPHYR_API_TOKEN;
|
|
29
|
+
const zephyrBaseUrl = process.env.ZEPHYR_BASE_URL;
|
|
27
30
|
let client_defined = false;
|
|
28
31
|
if (reflectToken) {
|
|
29
32
|
server.addClient(new ReflectClient(reflectToken));
|
|
@@ -56,8 +59,12 @@ async function main() {
|
|
|
56
59
|
server.addClient(new QmetryClient(qmetryToken, qmetryBaseUrl));
|
|
57
60
|
client_defined = true;
|
|
58
61
|
}
|
|
62
|
+
if (zephyrToken) {
|
|
63
|
+
server.addClient(new ZephyrClient(zephyrToken, zephyrBaseUrl));
|
|
64
|
+
client_defined = true;
|
|
65
|
+
}
|
|
59
66
|
if (!client_defined) {
|
|
60
|
-
console.error("Please set one of REFLECT_API_TOKEN, BUGSNAG_AUTH_TOKEN, API_HUB_API_KEY, QMETRY_API_KEY or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
|
|
67
|
+
console.error("Please set one of REFLECT_API_TOKEN, BUGSNAG_AUTH_TOKEN, API_HUB_API_KEY, QMETRY_API_KEY, ZEPHYR_API_TOKEN, or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
|
|
61
68
|
process.exit(1);
|
|
62
69
|
}
|
|
63
70
|
const transport = new StdioServerTransport();
|
|
@@ -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
|
/**
|
|
@@ -71,17 +71,17 @@ export const TOOLS = [
|
|
|
71
71
|
clients: ["pactflow", "pact_broker"],
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
|
-
title: "PactFlow AI
|
|
75
|
-
summary: "Check PactFlow AI
|
|
76
|
-
purpose: "Retrieve
|
|
74
|
+
title: "Check PactFlow AI Entitlements",
|
|
75
|
+
summary: "Check your PactFlow AI entitlements and credit balance if you encounter 401 Unauthorized errors or permission/credit issues when using PactFlow AI features.",
|
|
76
|
+
purpose: "Retrieve AI entitlement information when PactFlow AI operations fail with 401 unauthorized errors. Use this to diagnose permission issues, check remaining credits, and verify account eligibility for AI features.",
|
|
77
77
|
useCases: [
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
78
|
+
"Diagnose 401 unauthorized errors when attempting to use PactFlow AI features",
|
|
79
|
+
"Check remaining AI credits when PactFlow AI operations are rejected due to insufficient credits",
|
|
80
|
+
"Verify account entitlements when users receive permission denied errors for PactFlow AI functionality",
|
|
81
|
+
"Troubleshoot PactFlow AI access issues by retrieving current entitlement status and credit balance",
|
|
82
|
+
"Provide detailed error context when PactFlow AI features are unavailable due to account limitations",
|
|
83
83
|
],
|
|
84
|
-
handler: "
|
|
84
|
+
handler: "checkAIEntitlements",
|
|
85
85
|
clients: ["pactflow"],
|
|
86
86
|
},
|
|
87
87
|
];
|
|
@@ -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,20 +94,21 @@ 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");
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
|
-
*
|
|
103
|
-
* and organization.
|
|
103
|
+
* Retrieve PactFlow AI entitlement information for the current user
|
|
104
|
+
* and organization when encountering 401 unauthorized errors.
|
|
105
|
+
* Use this to check AI entitlements and credits when AI operations fail.
|
|
104
106
|
*
|
|
105
|
-
* @returns Entitlement containing
|
|
107
|
+
* @returns Entitlement containing permissions, organization
|
|
106
108
|
* entitlements, and user entitlements.
|
|
107
109
|
* @throws Error if the request fails or returns a non-OK response.
|
|
108
110
|
*/
|
|
109
|
-
async
|
|
111
|
+
async checkAIEntitlements() {
|
|
110
112
|
const url = `${this.aiBaseUrl}/entitlement`;
|
|
111
113
|
try {
|
|
112
114
|
const response = await fetch(url, {
|
|
@@ -115,12 +117,12 @@ export class PactflowClient {
|
|
|
115
117
|
});
|
|
116
118
|
if (!response.ok) {
|
|
117
119
|
const errorText = await response.text().catch(() => "");
|
|
118
|
-
throw new
|
|
120
|
+
throw new ToolError(`PactFlow AI Entitlements Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
119
121
|
}
|
|
120
122
|
return (await response.json());
|
|
121
123
|
}
|
|
122
124
|
catch (error) {
|
|
123
|
-
process.stderr.write(`[
|
|
125
|
+
process.stderr.write(`[CheckAIEntitlements] Unexpected error: ${error}\n`);
|
|
124
126
|
throw error;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
@@ -144,7 +146,7 @@ export class PactflowClient {
|
|
|
144
146
|
});
|
|
145
147
|
// Check if the response is OK (status 200)
|
|
146
148
|
if (!response.ok) {
|
|
147
|
-
throw new
|
|
149
|
+
throw new ToolError(`HTTP error! status: ${response.status}`);
|
|
148
150
|
}
|
|
149
151
|
return response.json();
|
|
150
152
|
}
|
|
@@ -160,12 +162,12 @@ export class PactflowClient {
|
|
|
160
162
|
return await this.getResult(status_response.result_url);
|
|
161
163
|
}
|
|
162
164
|
if (statusCheck.status !== 202) {
|
|
163
|
-
throw new
|
|
165
|
+
throw new ToolError(`${operationName} failed with status: ${statusCheck.status}`);
|
|
164
166
|
}
|
|
165
167
|
// Wait before next poll
|
|
166
168
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
167
169
|
}
|
|
168
|
-
throw new
|
|
170
|
+
throw new ToolError(`${operationName} timed out after ${timeout / 1000} seconds`);
|
|
169
171
|
}
|
|
170
172
|
// PactFlow / Pact_Broker client methods
|
|
171
173
|
async getProviderStates({ provider, }) {
|
|
@@ -175,7 +177,7 @@ export class PactflowClient {
|
|
|
175
177
|
headers: this.headers,
|
|
176
178
|
});
|
|
177
179
|
if (!response.ok) {
|
|
178
|
-
throw new
|
|
180
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
179
181
|
}
|
|
180
182
|
return response.json();
|
|
181
183
|
}
|
|
@@ -205,7 +207,7 @@ export class PactflowClient {
|
|
|
205
207
|
});
|
|
206
208
|
if (!response.ok) {
|
|
207
209
|
const errorText = await response.text().catch(() => "");
|
|
208
|
-
throw new
|
|
210
|
+
throw new ToolError(`Can-I-Deploy Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
209
211
|
}
|
|
210
212
|
return (await response.json());
|
|
211
213
|
}
|
|
@@ -264,7 +266,7 @@ export class PactflowClient {
|
|
|
264
266
|
});
|
|
265
267
|
if (!response.ok) {
|
|
266
268
|
const errorText = await response.text().catch(() => "");
|
|
267
|
-
throw new
|
|
269
|
+
throw new ToolError(`Matrix Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
268
270
|
}
|
|
269
271
|
return (await response.json());
|
|
270
272
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../../../common/info.js";
|
|
2
2
|
import { QMETRY_DEFAULTS } from "../../config/constants.js";
|
|
3
|
+
import { handleQMetryApiError, handleQMetryFetchError, } from "./error-handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* QMetry API request function with centralized error handling.
|
|
6
|
+
*
|
|
7
|
+
* Handles authentication, project access, CORS, and generic API errors with
|
|
8
|
+
* user-friendly messages and troubleshooting guidance.
|
|
9
|
+
*
|
|
10
|
+
* @param options Request configuration including authentication token
|
|
11
|
+
* @returns Parsed JSON response from QMetry API
|
|
12
|
+
* @throws User-friendly errors with detailed troubleshooting steps for various scenarios
|
|
13
|
+
*/
|
|
3
14
|
export async function qmetryRequest({ method = "GET", path, token, project, baseUrl, body, }) {
|
|
4
15
|
const url = `${baseUrl}${path}`;
|
|
5
16
|
const headers = {
|
|
@@ -17,23 +28,17 @@ export async function qmetryRequest({ method = "GET", path, token, project, base
|
|
|
17
28
|
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
18
29
|
init.body = JSON.stringify(body);
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
let res;
|
|
32
|
+
try {
|
|
33
|
+
res = await fetch(url, init);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Handle fetch errors (CORS, network issues, SSL certificate issues, etc.)
|
|
37
|
+
handleQMetryFetchError(error instanceof Error ? error : new Error(String(error)), baseUrl, project, path);
|
|
38
|
+
}
|
|
21
39
|
if (!res.ok) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const contentType = res.headers.get("content-type");
|
|
25
|
-
if (contentType?.includes("application/json")) {
|
|
26
|
-
const json = await res.json();
|
|
27
|
-
errorText = JSON.stringify(json);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
errorText = await res.text();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
errorText = res.statusText;
|
|
35
|
-
}
|
|
36
|
-
throw new Error(`QMetry API request failed (${res.status}): ${errorText}`);
|
|
40
|
+
// Use centralized error handling
|
|
41
|
+
await handleQMetryApiError(res, baseUrl, project, path);
|
|
37
42
|
}
|
|
38
43
|
return (await res.json());
|
|
39
44
|
}
|