@smartbear/mcp 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/dist/api-hub/client/api.js +387 -0
- package/dist/api-hub/client/configuration.js +27 -0
- package/dist/api-hub/client/index.js +5 -0
- package/dist/api-hub/client/portal-types.js +131 -0
- package/dist/api-hub/client/registry-types.js +69 -0
- package/dist/api-hub/client/tools.js +98 -0
- package/dist/api-hub/client.js +70 -404
- package/dist/bugsnag/client/api/CurrentUser.js +19 -13
- package/dist/bugsnag/client/api/Error.js +45 -57
- package/dist/bugsnag/client/api/Project.js +35 -30
- package/dist/bugsnag/client/api/base.js +24 -9
- package/dist/bugsnag/client/api/filters.js +9 -9
- package/dist/bugsnag/client.js +281 -373
- package/dist/common/info.js +1 -1
- package/dist/common/server.js +39 -28
- package/dist/index.js +18 -4
- package/dist/pactflow/client/ai.js +20 -20
- package/dist/pactflow/client/base.js +48 -13
- package/dist/pactflow/client/prompts.js +10 -12
- package/dist/pactflow/client/tools.js +18 -18
- package/dist/pactflow/client/utils.js +1 -1
- package/dist/pactflow/client.js +23 -15
- package/dist/qmetry/client/api/client-api.js +39 -0
- package/dist/qmetry/client/handlers.js +11 -0
- package/dist/qmetry/client/project.js +27 -0
- package/dist/qmetry/client/testcase.js +104 -0
- package/dist/qmetry/client/tools.js +222 -0
- package/dist/qmetry/client.js +95 -0
- package/dist/qmetry/config/constants.js +12 -0
- package/dist/qmetry/config/rest-endpoints.js +11 -0
- package/dist/qmetry/types/common.js +174 -0
- package/dist/qmetry/types/testcase.js +19 -0
- package/dist/reflect/client.js +14 -14
- package/dist/zephyr/client.js +16 -0
- package/dist/zephyr/common/api-client.js +27 -0
- package/dist/zephyr/common/auth-service.js +14 -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 +8 -6
package/dist/common/info.js
CHANGED
package/dist/common/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodString, ZodUnion, } from "zod";
|
|
1
|
+
import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodOptional, ZodString, ZodUnion, } from "zod";
|
|
3
3
|
import Bugsnag from "../common/bugsnag.js";
|
|
4
4
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
|
|
5
5
|
export class SmartBearMcpServer extends McpServer {
|
|
@@ -41,7 +41,9 @@ export class SmartBearMcpServer extends McpServer {
|
|
|
41
41
|
});
|
|
42
42
|
if (client.registerResources) {
|
|
43
43
|
client.registerResources((name, path, cb) => {
|
|
44
|
-
return super.registerResource(name, new ResourceTemplate(`${client.prefix}://${name}/${path}`, {
|
|
44
|
+
return super.registerResource(name, new ResourceTemplate(`${client.prefix}://${name}/${path}`, {
|
|
45
|
+
list: undefined,
|
|
46
|
+
}), {}, async (url, variables, extra) => {
|
|
45
47
|
try {
|
|
46
48
|
return await cb(url, variables, extra);
|
|
47
49
|
}
|
|
@@ -94,64 +96,73 @@ export class SmartBearMcpServer extends McpServer {
|
|
|
94
96
|
return args;
|
|
95
97
|
}
|
|
96
98
|
getDescription(params) {
|
|
97
|
-
const { summary, useCases, examples, parameters, zodSchema, hints, outputFormat } = params;
|
|
99
|
+
const { summary, useCases, examples, parameters, zodSchema, hints, outputFormat, } = params;
|
|
98
100
|
let description = summary;
|
|
99
101
|
// Parameters if available otherwise use zodSchema
|
|
100
102
|
if ((parameters ?? []).length > 0) {
|
|
101
|
-
description += `\n\n**Parameters:**\n${parameters
|
|
102
|
-
|
|
103
|
-
`${p.
|
|
104
|
-
`${p.
|
|
103
|
+
description += `\n\n**Parameters:**\n${parameters
|
|
104
|
+
?.map((p) => `- ${p.name} (${this.getReadableTypeName(p.type)})${p.required ? " *required*" : ""}` +
|
|
105
|
+
`${p.description ? `: ${p.description}` : ""}` +
|
|
106
|
+
`${p.examples ? ` (e.g. ${p.examples.join(", ")})` : ""}` +
|
|
107
|
+
`${p.constraints ? `\n - ${p.constraints.join("\n - ")}` : ""}`)
|
|
108
|
+
.join("\n")}`;
|
|
105
109
|
}
|
|
106
110
|
if (zodSchema && zodSchema instanceof ZodObject) {
|
|
107
111
|
description += "\n\n**Parameters:**\n";
|
|
108
112
|
description += Object.keys(zodSchema.shape)
|
|
109
|
-
.map(key => this.formatParameterDescription(key, zodSchema.shape[key]))
|
|
110
|
-
.join(
|
|
113
|
+
.map((key) => this.formatParameterDescription(key, zodSchema.shape[key]))
|
|
114
|
+
.join("\n");
|
|
111
115
|
}
|
|
112
116
|
if (outputFormat) {
|
|
113
117
|
description += `\n\n**Output Format:** ${outputFormat}`;
|
|
114
118
|
}
|
|
115
119
|
// Use Cases
|
|
116
120
|
if (useCases && useCases.length > 0) {
|
|
117
|
-
description += `\n\n**Use Cases:** ${useCases.map((uc, i) => `${i + 1}. ${uc}`).join(
|
|
121
|
+
description += `\n\n**Use Cases:** ${useCases.map((uc, i) => `${i + 1}. ${uc}`).join(" ")}`;
|
|
118
122
|
}
|
|
119
123
|
// Examples
|
|
120
124
|
if (examples && examples.length > 0) {
|
|
121
|
-
description +=
|
|
125
|
+
description +=
|
|
126
|
+
`\n\n**Examples:**\n` +
|
|
127
|
+
examples
|
|
128
|
+
.map((ex, idx) => `${idx + 1}. ${ex.description}\n\`\`\`json\n${JSON.stringify(ex.parameters, null, 2)}\n\`\`\`${ex.expectedOutput ? `\nExpected Output: ${ex.expectedOutput}` : ""}`)
|
|
129
|
+
.join("\n\n");
|
|
122
130
|
}
|
|
123
131
|
// Hints
|
|
124
132
|
if (hints && hints.length > 0) {
|
|
125
|
-
description += `\n\n**Hints:** ${hints.map((hint, i) => `${i + 1}. ${hint}`).join(
|
|
133
|
+
description += `\n\n**Hints:** ${hints.map((hint, i) => `${i + 1}. ${hint}`).join(" ")}`;
|
|
126
134
|
}
|
|
127
135
|
return description.trim();
|
|
128
136
|
}
|
|
129
137
|
formatParameterDescription(key, field) {
|
|
130
|
-
return `- ${key} (${this.getReadableTypeName(field)})` +
|
|
131
|
-
`${field.isOptional() ?
|
|
132
|
-
`${field.description ? `: ${field.description}` :
|
|
133
|
-
`${key === "examples" && field instanceof ZodEnum ? ` (e.g. ${Object.keys(field.enum).join(
|
|
134
|
-
`${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join(
|
|
138
|
+
return (`- ${key} (${this.getReadableTypeName(field)})` +
|
|
139
|
+
`${field.isOptional() ? "" : " *required*"}` +
|
|
140
|
+
`${field.description ? `: ${field.description}` : ""}` +
|
|
141
|
+
`${key === "examples" && field instanceof ZodEnum ? ` (e.g. ${Object.keys(field.enum).join(", ")})` : ""}` +
|
|
142
|
+
`${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join("\n - ")}` : ""}`);
|
|
135
143
|
}
|
|
136
144
|
getReadableTypeName(zodType) {
|
|
145
|
+
if (zodType instanceof ZodOptional) {
|
|
146
|
+
zodType = zodType._def.innerType;
|
|
147
|
+
}
|
|
137
148
|
if (zodType instanceof ZodString)
|
|
138
|
-
return
|
|
149
|
+
return "string";
|
|
139
150
|
if (zodType instanceof ZodNumber)
|
|
140
|
-
return
|
|
151
|
+
return "number";
|
|
141
152
|
if (zodType instanceof ZodBoolean)
|
|
142
|
-
return
|
|
153
|
+
return "boolean";
|
|
143
154
|
if (zodType instanceof ZodArray)
|
|
144
|
-
return
|
|
155
|
+
return "array";
|
|
145
156
|
if (zodType instanceof ZodObject)
|
|
146
|
-
return
|
|
157
|
+
return "object";
|
|
147
158
|
if (zodType instanceof ZodEnum)
|
|
148
|
-
return
|
|
159
|
+
return "enum";
|
|
149
160
|
if (zodType instanceof ZodLiteral)
|
|
150
|
-
return
|
|
161
|
+
return "literal";
|
|
151
162
|
if (zodType instanceof ZodUnion)
|
|
152
|
-
return
|
|
163
|
+
return "union";
|
|
153
164
|
if (zodType instanceof ZodAny)
|
|
154
|
-
return
|
|
155
|
-
return
|
|
165
|
+
return "any";
|
|
166
|
+
return "any";
|
|
156
167
|
}
|
|
157
168
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import Bugsnag from "./common/bugsnag.js";
|
|
4
|
-
import { BugsnagClient } from "./bugsnag/client.js";
|
|
5
|
-
import { ReflectClient } from "./reflect/client.js";
|
|
6
3
|
import { ApiHubClient } from "./api-hub/client.js";
|
|
4
|
+
import { BugsnagClient } from "./bugsnag/client.js";
|
|
5
|
+
import Bugsnag from "./common/bugsnag.js";
|
|
7
6
|
import { SmartBearMcpServer } from "./common/server.js";
|
|
8
7
|
import { PactflowClient } from "./pactflow/client.js";
|
|
8
|
+
import { QmetryClient } from "./qmetry/client.js";
|
|
9
|
+
import { ReflectClient } from "./reflect/client.js";
|
|
10
|
+
import { ZephyrClient } from "./zephyr/client.js";
|
|
9
11
|
// This is used to report errors in the MCP server itself
|
|
10
12
|
// If you want to use your own BugSnag API key, set the MCP_SERVER_BUGSNAG_API_KEY environment variable
|
|
11
13
|
const McpServerBugsnagAPIKey = process.env.MCP_SERVER_BUGSNAG_API_KEY;
|
|
@@ -21,6 +23,10 @@ async function main() {
|
|
|
21
23
|
const pactBrokerUrl = process.env.PACT_BROKER_BASE_URL;
|
|
22
24
|
const pactBrokerUsername = process.env.PACT_BROKER_USERNAME;
|
|
23
25
|
const pactBrokerPassword = process.env.PACT_BROKER_PASSWORD;
|
|
26
|
+
const qmetryToken = process.env.QMETRY_API_KEY;
|
|
27
|
+
const qmetryBaseUrl = process.env.QMETRY_BASE_URL;
|
|
28
|
+
const zephyrToken = process.env.ZEPHYR_API_TOKEN;
|
|
29
|
+
const zephyrBaseUrl = process.env.ZEPHYR_BASE_URL;
|
|
24
30
|
let client_defined = false;
|
|
25
31
|
if (reflectToken) {
|
|
26
32
|
server.addClient(new ReflectClient(reflectToken));
|
|
@@ -49,8 +55,16 @@ async function main() {
|
|
|
49
55
|
console.error("If the Pact Broker base URL is specified, you must specify either (a) a PactFlow token, or (b) a Pact Broker username and password pair.");
|
|
50
56
|
}
|
|
51
57
|
}
|
|
58
|
+
if (qmetryToken) {
|
|
59
|
+
server.addClient(new QmetryClient(qmetryToken, qmetryBaseUrl));
|
|
60
|
+
client_defined = true;
|
|
61
|
+
}
|
|
62
|
+
if (zephyrToken) {
|
|
63
|
+
server.addClient(new ZephyrClient(zephyrToken, zephyrBaseUrl));
|
|
64
|
+
client_defined = true;
|
|
65
|
+
}
|
|
52
66
|
if (!client_defined) {
|
|
53
|
-
console.error("Please set one of REFLECT_API_TOKEN, BUGSNAG_AUTH_TOKEN, API_HUB_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");
|
|
54
68
|
process.exit(1);
|
|
55
69
|
}
|
|
56
70
|
const transport = new StdioServerTransport();
|
|
@@ -146,18 +146,15 @@ export const GenerationInputSchema = z.object({
|
|
|
146
146
|
testTemplate: FileInputSchema.optional().describe("Optional test template to use as a basis for generation. Helps ensure generated tests follow your specific patterns, frameworks, and coding standards"),
|
|
147
147
|
});
|
|
148
148
|
export const MatcherRecommendationInputSchema = z.array(EndpointMatcherSchema);
|
|
149
|
-
export const AiCreditsSchema = z
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
name: z
|
|
159
|
-
.string()
|
|
160
|
-
.describe("The name of the organization."),
|
|
149
|
+
export const AiCreditsSchema = z
|
|
150
|
+
.object({
|
|
151
|
+
total: z.number().describe("The total number of AI credits available."),
|
|
152
|
+
used: z.number().describe("The number of AI credits used."),
|
|
153
|
+
})
|
|
154
|
+
.describe("AI credits information.");
|
|
155
|
+
export const OrganizationEntitlementsSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
name: z.string().describe("The name of the organization."),
|
|
161
158
|
planAiEnabled: z
|
|
162
159
|
.boolean()
|
|
163
160
|
.describe("Whether AI features are enabled at the plan level."),
|
|
@@ -165,13 +162,16 @@ export const OrganizationEntitlementsSchema = z.object({
|
|
|
165
162
|
.boolean()
|
|
166
163
|
.describe("Whether AI features are enabled at the preferences level."),
|
|
167
164
|
aiCredits: AiCreditsSchema.describe("AI credits information."),
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
})
|
|
174
|
-
|
|
165
|
+
})
|
|
166
|
+
.describe("Organization entitlements information.");
|
|
167
|
+
export const UserEntitlementsSchema = z
|
|
168
|
+
.object({
|
|
169
|
+
aiPermissions: z.array(z.string()).describe("List of AI permissions."),
|
|
170
|
+
})
|
|
171
|
+
.describe("User entitlements information.");
|
|
172
|
+
export const EntitlementsSchema = z
|
|
173
|
+
.object({
|
|
175
174
|
organizationEntitlements: OrganizationEntitlementsSchema.describe("Organization entitlements information."),
|
|
176
175
|
userEntitlements: UserEntitlementsSchema.describe("User entitlements information."),
|
|
177
|
-
})
|
|
176
|
+
})
|
|
177
|
+
.describe("Entitlements information.");
|
|
@@ -1,19 +1,54 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const CanIDeploySchema = z.object({
|
|
3
|
-
pacticipant: z
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
pacticipant: z
|
|
4
|
+
.string()
|
|
5
|
+
.describe("The name of the pacticipant (application/service) being evaluated for deployment"),
|
|
6
|
+
version: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("The version of the pacticipant that you want to check if it's safe to deploy"),
|
|
9
|
+
environment: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("The target environment where the pacticipant version will be deployed (e.g., 'production', 'staging', 'test')"),
|
|
6
12
|
});
|
|
7
13
|
export const MatrixSchema = z.object({
|
|
8
|
-
latestby: z
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
latestby: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("This property removes the rows for the overridden pacts/verifications from the results. The options are cvp (show only the latest row for each consumer version and provider) and cvpv (show only the latest row each consumer version and provider version). For a can-i-deploy query with one selector, it should be set to cvp. For a can-i-deploy query with two selectors, it should be set to cvpv."),
|
|
18
|
+
limit: z
|
|
19
|
+
.number()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(1000)
|
|
22
|
+
.default(100)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("The limit on the number of results to return (1-1000, default: 100)"),
|
|
25
|
+
q: z
|
|
26
|
+
.array(z.object({
|
|
27
|
+
pacticipant: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe("Name of the pacticipant (application)"),
|
|
12
30
|
version: z.string().optional().describe("Version number"),
|
|
13
|
-
branch: z
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
branch: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Name of the pacticipant version branch"),
|
|
35
|
+
environment: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("The name of the environment that the pacticipant version is deployed to"),
|
|
39
|
+
latest: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Used in conjunction with other properties to indicate whether the selector is describing the latest version from a branch/with a tag/for a pacticipant, or all of them. Note that when used with tags, the 'latest' is calculated using the creation date of the pacticipant version, NOT the creation date of the tag."),
|
|
43
|
+
tag: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("The name of the pacticipant version tag (superseded by branch and environments)"),
|
|
47
|
+
mainBranch: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Whether or not the version(s) described are from the main branch of the pacticipant, as set in the mainBranch property of the pacticipant resource."),
|
|
51
|
+
}))
|
|
52
|
+
.min(1)
|
|
53
|
+
.max(2),
|
|
19
54
|
});
|
|
@@ -116,18 +116,16 @@ export const PROMPTS = [
|
|
|
116
116
|
openAPI: z.string(),
|
|
117
117
|
},
|
|
118
118
|
},
|
|
119
|
-
callback:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
text: OADMatcherPrompt.replace("{0}", openAPI),
|
|
127
|
-
},
|
|
119
|
+
callback: ({ openAPI }) => ({
|
|
120
|
+
messages: [
|
|
121
|
+
{
|
|
122
|
+
role: "user",
|
|
123
|
+
content: {
|
|
124
|
+
type: "text",
|
|
125
|
+
text: OADMatcherPrompt.replace("{0}", openAPI),
|
|
128
126
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
}),
|
|
132
130
|
},
|
|
133
131
|
];
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* and registered with their corresponding handler.
|
|
11
11
|
*/
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
import { GenerationInputSchema, RefineInputSchema
|
|
13
|
+
import { GenerationInputSchema, RefineInputSchema } from "./ai.js";
|
|
14
14
|
import { CanIDeploySchema, MatrixSchema } from "./base.js";
|
|
15
15
|
export const TOOLS = [
|
|
16
16
|
{
|
|
@@ -40,11 +40,11 @@ export const TOOLS = [
|
|
|
40
40
|
name: "provider",
|
|
41
41
|
type: z.string(),
|
|
42
42
|
description: "name of the provider to retrieve states for",
|
|
43
|
-
required: true
|
|
44
|
-
}
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
45
|
],
|
|
46
46
|
handler: "getProviderStates",
|
|
47
|
-
clients: ["pactflow", "pact_broker"]
|
|
47
|
+
clients: ["pactflow", "pact_broker"],
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
title: "Can I Deploy",
|
|
@@ -52,7 +52,7 @@ export const TOOLS = [
|
|
|
52
52
|
purpose: "To serve as a deployment safety check within the PactBroker and PactFlow ecosystem, leveraging contract testing results to validate whether a specific service / pacticipant version is compatible with all integrated services. This feature prevents unsafe releases, reduces integration risks, and enables teams to confidently automate deployments across environments with a clear, auditable record of verification results.",
|
|
53
53
|
zodSchema: CanIDeploySchema,
|
|
54
54
|
handler: "canIDeploy",
|
|
55
|
-
clients: ["pactflow", "pact_broker"]
|
|
55
|
+
clients: ["pactflow", "pact_broker"],
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
title: "Matrix",
|
|
@@ -64,24 +64,24 @@ export const TOOLS = [
|
|
|
64
64
|
"Visualize the overall contract compatibility across two pacticipants / services.",
|
|
65
65
|
"Perform advanced queries using selectors to understand compatibility within specific branches, environments, or version ranges.",
|
|
66
66
|
"Support informed deployment decisions by answering 'can I deploy version X of this service to production?'",
|
|
67
|
-
"Expose contract verification details to non-frequent API users in a more accessible format."
|
|
67
|
+
"Expose contract verification details to non-frequent API users in a more accessible format.",
|
|
68
68
|
],
|
|
69
69
|
zodSchema: MatrixSchema,
|
|
70
70
|
handler: "getMatrix",
|
|
71
|
-
clients: ["pactflow", "pact_broker"]
|
|
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: "
|
|
85
|
-
clients: ["pactflow"]
|
|
86
|
-
}
|
|
84
|
+
handler: "checkAIEntitlements",
|
|
85
|
+
clients: ["pactflow"],
|
|
86
|
+
},
|
|
87
87
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { RemoteOpenAPIDocumentSchema, } from "./ai.js";
|
|
2
1
|
import yaml from "js-yaml";
|
|
3
2
|
// @ts-expect-error missing type declarations
|
|
4
3
|
import Swagger from "swagger-client";
|
|
4
|
+
import { RemoteOpenAPIDocumentSchema, } from "./ai.js";
|
|
5
5
|
/**
|
|
6
6
|
* Resolve the OpenAPI specification from the provided input.
|
|
7
7
|
*
|
package/dist/pactflow/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
2
|
-
import {
|
|
3
|
-
import { getOADMatcherRecommendations, getUserMatcherSelection } from "./client/prompt-utils.js";
|
|
2
|
+
import { getOADMatcherRecommendations, getUserMatcherSelection, } from "./client/prompt-utils.js";
|
|
4
3
|
import { PROMPTS } from "./client/prompts.js";
|
|
4
|
+
import { TOOLS } from "./client/tools.js";
|
|
5
5
|
// Tool definitions for PactFlow AI API client
|
|
6
6
|
export class PactflowClient {
|
|
7
7
|
name = "Contract Testing";
|
|
@@ -51,7 +51,9 @@ export class PactflowClient {
|
|
|
51
51
|
* @throws Error if the HTTP request fails or the operation times out.
|
|
52
52
|
*/
|
|
53
53
|
async generate(toolInput, getInput) {
|
|
54
|
-
if (toolInput.openapi?.document &&
|
|
54
|
+
if (toolInput.openapi?.document &&
|
|
55
|
+
(!toolInput.openapi?.matcher ||
|
|
56
|
+
Object.keys(toolInput.openapi.matcher).length === 0)) {
|
|
55
57
|
const matcherResponse = await getOADMatcherRecommendations(toolInput.openapi.document, this.server);
|
|
56
58
|
const userSelection = await getUserMatcherSelection(matcherResponse, getInput);
|
|
57
59
|
toolInput.openapi.matcher = userSelection;
|
|
@@ -77,7 +79,9 @@ export class PactflowClient {
|
|
|
77
79
|
* @throws Error if the HTTP request fails or the operation times out.
|
|
78
80
|
*/
|
|
79
81
|
async review(toolInput, getInput) {
|
|
80
|
-
if (toolInput.openapi?.document &&
|
|
82
|
+
if (toolInput.openapi?.document &&
|
|
83
|
+
(!toolInput.openapi?.matcher ||
|
|
84
|
+
Object.keys(toolInput.openapi.matcher).length === 0)) {
|
|
81
85
|
const matcherResponse = await getOADMatcherRecommendations(toolInput.openapi.document, this.server);
|
|
82
86
|
const userSelection = await getUserMatcherSelection(matcherResponse, getInput);
|
|
83
87
|
toolInput.openapi.matcher = userSelection;
|
|
@@ -95,14 +99,15 @@ export class PactflowClient {
|
|
|
95
99
|
return await this.pollForCompletion(status_response, "Review Pacts");
|
|
96
100
|
}
|
|
97
101
|
/**
|
|
98
|
-
*
|
|
99
|
-
* and organization.
|
|
102
|
+
* Retrieve PactFlow AI entitlement information for the current user
|
|
103
|
+
* and organization when encountering 401 unauthorized errors.
|
|
104
|
+
* Use this to check AI entitlements and credits when AI operations fail.
|
|
100
105
|
*
|
|
101
|
-
* @returns Entitlement containing
|
|
106
|
+
* @returns Entitlement containing permissions, organization
|
|
102
107
|
* entitlements, and user entitlements.
|
|
103
108
|
* @throws Error if the request fails or returns a non-OK response.
|
|
104
109
|
*/
|
|
105
|
-
async
|
|
110
|
+
async checkAIEntitlements() {
|
|
106
111
|
const url = `${this.aiBaseUrl}/entitlement`;
|
|
107
112
|
try {
|
|
108
113
|
const response = await fetch(url, {
|
|
@@ -111,12 +116,12 @@ export class PactflowClient {
|
|
|
111
116
|
});
|
|
112
117
|
if (!response.ok) {
|
|
113
118
|
const errorText = await response.text().catch(() => "");
|
|
114
|
-
throw new Error(`PactFlow AI
|
|
119
|
+
throw new Error(`PactFlow AI Entitlements Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
115
120
|
}
|
|
116
121
|
return (await response.json());
|
|
117
122
|
}
|
|
118
123
|
catch (error) {
|
|
119
|
-
process.stderr.write(`[
|
|
124
|
+
process.stderr.write(`[CheckAIEntitlements] Unexpected error: ${error}\n`);
|
|
120
125
|
throw error;
|
|
121
126
|
}
|
|
122
127
|
}
|
|
@@ -130,6 +135,9 @@ export class PactflowClient {
|
|
|
130
135
|
isComplete: response.status === 200,
|
|
131
136
|
};
|
|
132
137
|
}
|
|
138
|
+
get requestHeaders() {
|
|
139
|
+
return this.headers;
|
|
140
|
+
}
|
|
133
141
|
async getResult(resultUrl) {
|
|
134
142
|
const response = await fetch(resultUrl, {
|
|
135
143
|
method: "GET",
|
|
@@ -161,7 +169,7 @@ export class PactflowClient {
|
|
|
161
169
|
throw new Error(`${operationName} timed out after ${timeout / 1000} seconds`);
|
|
162
170
|
}
|
|
163
171
|
// PactFlow / Pact_Broker client methods
|
|
164
|
-
async getProviderStates({ provider }) {
|
|
172
|
+
async getProviderStates({ provider, }) {
|
|
165
173
|
const uri_encoded_provider_name = encodeURIComponent(provider);
|
|
166
174
|
const response = await fetch(`${this.baseUrl}/pacts/provider/${uri_encoded_provider_name}/provider-states`, {
|
|
167
175
|
method: "GET",
|
|
@@ -249,7 +257,7 @@ export class PactflowClient {
|
|
|
249
257
|
queryParts.push(`q[]mainBranch=${selector.mainBranch}`);
|
|
250
258
|
}
|
|
251
259
|
});
|
|
252
|
-
const url = `${this.baseUrl}/matrix?${queryParts.join(
|
|
260
|
+
const url = `${this.baseUrl}/matrix?${queryParts.join("&")}`;
|
|
253
261
|
try {
|
|
254
262
|
const response = await fetch(url, {
|
|
255
263
|
method: "GET",
|
|
@@ -273,8 +281,8 @@ export class PactflowClient {
|
|
|
273
281
|
* @param getInput - The function used to get input for tools.
|
|
274
282
|
*/
|
|
275
283
|
registerTools(register, getInput) {
|
|
276
|
-
for (const tool of TOOLS.filter(t => t.clients.includes(this.clientType))) {
|
|
277
|
-
const { handler, clients: _, formatResponse, ...toolparams } = tool;
|
|
284
|
+
for (const tool of TOOLS.filter((t) => t.clients.includes(this.clientType))) {
|
|
285
|
+
const { handler, clients: _, formatResponse, ...toolparams } = tool;
|
|
278
286
|
register(toolparams, async (args, _extra) => {
|
|
279
287
|
const handler_fn = this[handler];
|
|
280
288
|
if (typeof handler_fn !== "function") {
|
|
@@ -304,7 +312,7 @@ export class PactflowClient {
|
|
|
304
312
|
* @param register - The function used to register prompts.
|
|
305
313
|
*/
|
|
306
314
|
registerPrompts(register) {
|
|
307
|
-
PROMPTS.forEach(prompt => {
|
|
315
|
+
PROMPTS.forEach((prompt) => {
|
|
308
316
|
register(prompt.name, prompt.params, prompt.callback);
|
|
309
317
|
});
|
|
310
318
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../../../common/info.js";
|
|
2
|
+
import { QMETRY_DEFAULTS } from "../../config/constants.js";
|
|
3
|
+
export async function qmetryRequest({ method = "GET", path, token, project, baseUrl, body, }) {
|
|
4
|
+
const url = `${baseUrl}${path}`;
|
|
5
|
+
const headers = {
|
|
6
|
+
apikey: token,
|
|
7
|
+
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
8
|
+
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
|
|
9
|
+
};
|
|
10
|
+
if (body) {
|
|
11
|
+
headers["Content-Type"] = "application/json";
|
|
12
|
+
}
|
|
13
|
+
const init = {
|
|
14
|
+
method,
|
|
15
|
+
headers,
|
|
16
|
+
};
|
|
17
|
+
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
18
|
+
init.body = JSON.stringify(body);
|
|
19
|
+
}
|
|
20
|
+
const res = await fetch(url, init);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
let errorText;
|
|
23
|
+
try {
|
|
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}`);
|
|
37
|
+
}
|
|
38
|
+
return (await res.json());
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { QMetryToolsHandlers } from "../config/constants.js";
|
|
2
|
+
import { getProjectInfo } from "./project.js";
|
|
3
|
+
import { fetchTestCaseDetails, fetchTestCaseSteps, fetchTestCases, fetchTestCaseVersionDetails, } from "./testcase.js";
|
|
4
|
+
export const QMETRY_HANDLER_MAP = {
|
|
5
|
+
[QMetryToolsHandlers.SET_PROJECT_INFO]: getProjectInfo,
|
|
6
|
+
[QMetryToolsHandlers.FETCH_PROJECT_INFO]: getProjectInfo,
|
|
7
|
+
[QMetryToolsHandlers.FETCH_TEST_CASES]: fetchTestCases,
|
|
8
|
+
[QMetryToolsHandlers.FETCH_TEST_CASE_DETAILS]: fetchTestCaseDetails,
|
|
9
|
+
[QMetryToolsHandlers.FETCH_TEST_CASE_VERSION_DETAILS]: fetchTestCaseVersionDetails,
|
|
10
|
+
[QMetryToolsHandlers.FETCH_TEST_CASE_STEPS]: fetchTestCaseSteps,
|
|
11
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { QMETRY_DEFAULTS } from "../config/constants.js";
|
|
2
|
+
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
|
|
3
|
+
import { qmetryRequest } from "./api/client-api.js";
|
|
4
|
+
/**
|
|
5
|
+
* Retrieves project information from QMetry
|
|
6
|
+
*
|
|
7
|
+
* This function serves dual purpose:
|
|
8
|
+
* 1. SET_PROJECT_INFO - Sets/switches the current project context
|
|
9
|
+
* 2. FETCH_PROJECT_INFO - Retrieves project details and configuration
|
|
10
|
+
*
|
|
11
|
+
* Both operations use the same API endpoint as QMetry handles project context
|
|
12
|
+
* switching and information retrieval through the same GET request.
|
|
13
|
+
*
|
|
14
|
+
* @param token - QMetry API authentication token
|
|
15
|
+
* @param baseUrl - QMetry instance base URL (defaults to configured URL)
|
|
16
|
+
* @param project - Project key to retrieve info for (defaults to configured project)
|
|
17
|
+
* @returns Promise resolving to project information including viewIds, folders, and configuration
|
|
18
|
+
*/
|
|
19
|
+
export async function getProjectInfo(token, baseUrl, project) {
|
|
20
|
+
return qmetryRequest({
|
|
21
|
+
method: "GET",
|
|
22
|
+
path: QMETRY_PATHS.PROJECT.GET_INFO,
|
|
23
|
+
token,
|
|
24
|
+
baseUrl: baseUrl || QMETRY_DEFAULTS.BASE_URL,
|
|
25
|
+
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
26
|
+
});
|
|
27
|
+
}
|