@smartbear/mcp 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +15 -121
  2. package/dist/{insight-hub → bugsnag}/client/api/CurrentUser.js +4 -4
  3. package/dist/{insight-hub → bugsnag}/client/api/Error.js +37 -4
  4. package/dist/bugsnag/client/api/Project.js +163 -0
  5. package/dist/{insight-hub → bugsnag}/client/api/base.js +39 -11
  6. package/dist/{insight-hub → bugsnag}/client/api/filters.js +2 -2
  7. package/dist/{insight-hub → bugsnag}/client.js +511 -29
  8. package/dist/common/info.js +1 -1
  9. package/dist/common/server.js +17 -5
  10. package/dist/index.js +11 -11
  11. package/dist/pactflow/client/ai.js +56 -6
  12. package/dist/pactflow/client/base.js +19 -1
  13. package/dist/pactflow/client/prompt-utils.js +89 -0
  14. package/dist/pactflow/client/prompts.js +133 -0
  15. package/dist/pactflow/client/tools.js +43 -2
  16. package/dist/pactflow/client/utils.js +70 -0
  17. package/dist/pactflow/client.js +192 -13
  18. package/package.json +9 -4
  19. package/dist/insight-hub/client/api/Project.js +0 -46
  20. package/dist/package.json +0 -60
  21. package/dist/tests/unit/common/server.test.js +0 -319
  22. package/dist/tests/unit/insight-hub/api-utilities.test.js +0 -31
  23. package/dist/tests/unit/insight-hub/client.test.js +0 -852
  24. package/dist/tests/unit/insight-hub/filters.test.js +0 -93
  25. package/dist/tests/unit/pactflow/ai.test.js +0 -21
  26. package/dist/tests/unit/pactflow/client.test.js +0 -67
  27. package/dist/tests/unit/pactflow/tools.test.js +0 -34
  28. package/dist/vitest.config.js +0 -57
  29. /package/dist/{insight-hub → bugsnag}/client/api/index.js +0 -0
  30. /package/dist/{insight-hub → bugsnag}/client/configuration.js +0 -0
  31. /package/dist/{insight-hub → bugsnag}/client/index.js +0 -0
@@ -1,3 +1,3 @@
1
- import packageJson from '../package.json' with { type: "json" };
1
+ import packageJson from '../../package.json' with { type: "json" };
2
2
  export const MCP_SERVER_NAME = packageJson.config.mcpServerName;
3
3
  export const MCP_SERVER_VERSION = packageJson.version;
@@ -11,6 +11,10 @@ export class SmartBearMcpServer extends McpServer {
11
11
  capabilities: {
12
12
  resources: { listChanged: true }, // Server supports dynamic resource lists
13
13
  tools: { listChanged: true }, // Server supports dynamic tool lists
14
+ sampling: {}, // Server supports sampling requests to Host
15
+ elicitation: {}, // Server supports eliciting input from the user
16
+ logging: {}, // Server supports logging messages
17
+ prompts: {}, // Server supports sending prompts to Host
14
18
  },
15
19
  });
16
20
  }
@@ -48,6 +52,11 @@ export class SmartBearMcpServer extends McpServer {
48
52
  });
49
53
  });
50
54
  }
55
+ if (client.registerPrompts) {
56
+ client.registerPrompts((name, config, cb) => {
57
+ return super.registerPrompt(name, config, cb);
58
+ });
59
+ }
51
60
  }
52
61
  getAnnotations(toolTitle, params) {
53
62
  const annotations = {
@@ -96,10 +105,9 @@ export class SmartBearMcpServer extends McpServer {
96
105
  }
97
106
  if (zodSchema && zodSchema instanceof ZodObject) {
98
107
  description += "\n\n**Parameters:**\n";
99
- for (const key of Object.keys(zodSchema.shape)) {
100
- const field = zodSchema.shape[key];
101
- description += this.formatParameterDescription(key, field);
102
- }
108
+ description += Object.keys(zodSchema.shape)
109
+ .map(key => this.formatParameterDescription(key, zodSchema.shape[key]))
110
+ .join('\n');
103
111
  }
104
112
  if (outputFormat) {
105
113
  description += `\n\n**Output Format:** ${outputFormat}`;
@@ -119,7 +127,11 @@ export class SmartBearMcpServer extends McpServer {
119
127
  return description.trim();
120
128
  }
121
129
  formatParameterDescription(key, field) {
122
- return `- ${key} (${this.getReadableTypeName(field)})${field.isOptional() ? '' : ' *required*'}${field.description ? `: ${field.description}` : ''}${key === "examples" && field instanceof ZodEnum ? ` (e.g. ${Object.keys(field.enum).join(', ')})` : ''}${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join('\n - ')}` : ''} \n`;
130
+ return `- ${key} (${this.getReadableTypeName(field)})` +
131
+ `${field.isOptional() ? '' : ' *required*'}` +
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('\n - ')}` : ''}`;
123
135
  }
124
136
  getReadableTypeName(zodType) {
125
137
  if (zodType instanceof ZodString)
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import Bugsnag from "./common/bugsnag.js";
4
- import { InsightHubClient } from "./insight-hub/client.js";
4
+ import { BugsnagClient } from "./bugsnag/client.js";
5
5
  import { ReflectClient } from "./reflect/client.js";
6
6
  import { ApiHubClient } from "./api-hub/client.js";
7
7
  import { SmartBearMcpServer } from "./common/server.js";
8
8
  import { PactflowClient } from "./pactflow/client.js";
9
9
  // This is used to report errors in the MCP server itself
10
- // If you want to use your own BugSnag API key, set the MCP_SERVER_INSIGHT_HUB_API_KEY environment variable
11
- const McpServerBugsnagAPIKey = process.env.MCP_SERVER_INSIGHT_HUB_API_KEY;
10
+ // If you want to use your own BugSnag API key, set the MCP_SERVER_BUGSNAG_API_KEY environment variable
11
+ const McpServerBugsnagAPIKey = process.env.MCP_SERVER_BUGSNAG_API_KEY;
12
12
  if (McpServerBugsnagAPIKey) {
13
13
  Bugsnag.start(McpServerBugsnagAPIKey);
14
14
  }
15
15
  async function main() {
16
16
  const server = new SmartBearMcpServer();
17
17
  const reflectToken = process.env.REFLECT_API_TOKEN;
18
- const insightHubToken = process.env.INSIGHT_HUB_AUTH_TOKEN;
18
+ const bugsnagToken = process.env.BUGSNAG_AUTH_TOKEN;
19
19
  const apiHubToken = process.env.API_HUB_API_KEY;
20
20
  const pactBrokerToken = process.env.PACT_BROKER_TOKEN;
21
21
  const pactBrokerUrl = process.env.PACT_BROKER_BASE_URL;
@@ -26,10 +26,10 @@ async function main() {
26
26
  server.addClient(new ReflectClient(reflectToken));
27
27
  client_defined = true;
28
28
  }
29
- if (insightHubToken) {
30
- const insightHubClient = new InsightHubClient(insightHubToken, process.env.INSIGHT_HUB_PROJECT_API_KEY, process.env.INSIGHT_HUB_ENDPOINT);
31
- await insightHubClient.initialize();
32
- server.addClient(insightHubClient);
29
+ if (bugsnagToken) {
30
+ const bugsnagClient = new BugsnagClient(bugsnagToken, process.env.BUGSNAG_PROJECT_API_KEY, process.env.BUGSNAG_ENDPOINT);
31
+ await bugsnagClient.initialize();
32
+ server.addClient(bugsnagClient);
33
33
  client_defined = true;
34
34
  }
35
35
  if (apiHubToken) {
@@ -38,11 +38,11 @@ async function main() {
38
38
  }
39
39
  if (pactBrokerUrl) {
40
40
  if (pactBrokerToken) {
41
- server.addClient(new PactflowClient(pactBrokerToken, pactBrokerUrl, "pactflow"));
41
+ server.addClient(new PactflowClient(pactBrokerToken, pactBrokerUrl, "pactflow", server.server));
42
42
  client_defined = true;
43
43
  }
44
44
  else if (pactBrokerUsername && pactBrokerPassword) {
45
- server.addClient(new PactflowClient({ username: pactBrokerUsername, password: pactBrokerPassword }, pactBrokerUrl, "pact_broker"));
45
+ server.addClient(new PactflowClient({ username: pactBrokerUsername, password: pactBrokerPassword }, pactBrokerUrl, "pact_broker", server.server));
46
46
  client_defined = true;
47
47
  }
48
48
  else {
@@ -50,7 +50,7 @@ async function main() {
50
50
  }
51
51
  }
52
52
  if (!client_defined) {
53
- console.error("Please set one of REFLECT_API_TOKEN, INSIGHT_HUB_AUTH_TOKEN, API_HUB_API_KEY or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
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");
54
54
  process.exit(1);
55
55
  }
56
56
  const transport = new StdioServerTransport();
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { addOpenAPISpecToSchema } from "./utils.js";
2
3
  // Type definitions for PactFlow AI API
3
4
  export const GenerationLanguages = [
4
5
  "javascript",
@@ -53,12 +54,11 @@ export const OpenAPISchema = z
53
54
  .describe("OpenAPI components section (schemas, responses, etc.)"),
54
55
  })
55
56
  .passthrough()
56
- .describe("The complete OpenAPI document describing the API")
57
+ .describe("The complete OpenAPI document describing the API.")
57
58
  .refine((data) => data.openapi || data.swagger, {
58
59
  message: "Either 'openapi' (for v3+) or 'swagger' (for v2) must be provided",
59
60
  path: ["openapi"],
60
- })
61
- .optional();
61
+ });
62
62
  export const EndpointMatcherSchema = z
63
63
  .object({
64
64
  path: z
@@ -78,14 +78,34 @@ export const EndpointMatcherSchema = z
78
78
  .optional()
79
79
  .describe("OpenAPI operation ID to match (e.g., 'getUserById', 'get*'). Supports glob patterns"),
80
80
  })
81
- .required()
81
+ .optional()
82
82
  .describe("REQUIRED: Matcher to specify which endpoints from the OpenAPI document to generate tests for. At least one matcher field must be provided");
83
+ export const RemoteOpenAPIDocumentSchema = z
84
+ .object({
85
+ authToken: z
86
+ .string()
87
+ .describe("Auth Bearer Token if the OpenAPI spec requires authentication.")
88
+ .optional(),
89
+ authScheme: z
90
+ .string()
91
+ .describe("Authentication scheme (e.g., 'Bearer', 'Basic'). Default scheme passed should be Bearer if authToken is specified and this field is not set.")
92
+ .default("Bearer")
93
+ .optional(),
94
+ url: z
95
+ .string()
96
+ .url("Must be a valid openapi url")
97
+ .describe("URL of the remote OpenAPI document.")
98
+ .optional(),
99
+ })
100
+ .describe("Use this schema to fetch openapi documents present over a url.");
83
101
  export const OpenAPIWithMatcherSchema = z
84
102
  .object({
85
- document: OpenAPISchema,
103
+ document: OpenAPISchema.describe("The OpenAPI document describing the API being tested. if document is not provided, don't add the field if remoteOpenAPIDocument is provided.").optional(),
86
104
  matcher: EndpointMatcherSchema,
105
+ remoteDocument: RemoteOpenAPIDocumentSchema.optional().describe("The remote OpenAPI document to use for the review/generation in case openapi document is not provided. If provided do not include the document field under openapi."),
87
106
  })
88
- .describe("If provided, the OpenAPI document which describes the API being tested and is accompanied by a matcher which will be used to identify the interactions in the OpenAPI document which are relevant to the Pact refinement process.");
107
+ .describe("If provided, the OpenAPI document which describes the API being tested and is accompanied by a matcher which will be used to identify the interactions in the OpenAPI document which are relevant to the Pact refinement process.")
108
+ .transform(addOpenAPISpecToSchema);
89
109
  export const RefineInputSchema = z.object({
90
110
  pactTests: FileInputSchema.describe("Primary pact tests that needs to be refined."),
91
111
  code: z
@@ -125,3 +145,33 @@ export const GenerationInputSchema = z.object({
125
145
  .describe("Optional free-form instructions to guide the generation process (e.g., 'Focus on error scenarios', 'Include authentication headers', 'Use specific test framework patterns')"),
126
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"),
127
147
  });
148
+ export const MatcherRecommendationInputSchema = z.array(EndpointMatcherSchema);
149
+ export const AiCreditsSchema = z.object({
150
+ total: z
151
+ .number()
152
+ .describe("The total number of AI credits available."),
153
+ used: z
154
+ .number()
155
+ .describe("The number of AI credits used."),
156
+ }).describe("AI credits information.");
157
+ export const OrganizationEntitlementsSchema = z.object({
158
+ name: z
159
+ .string()
160
+ .describe("The name of the organization."),
161
+ planAiEnabled: z
162
+ .boolean()
163
+ .describe("Whether AI features are enabled at the plan level."),
164
+ preferencesAiEnabled: z
165
+ .boolean()
166
+ .describe("Whether AI features are enabled at the preferences level."),
167
+ aiCredits: AiCreditsSchema.describe("AI credits information."),
168
+ }).describe("Organization entitlements information.");
169
+ export const UserEntitlementsSchema = z.object({
170
+ aiPermissions: z
171
+ .array(z.string())
172
+ .describe("List of AI permissions."),
173
+ }).describe("User entitlements information.");
174
+ export const EntitlementsSchema = z.object({
175
+ organizationEntitlements: OrganizationEntitlementsSchema.describe("Organization entitlements information."),
176
+ userEntitlements: UserEntitlementsSchema.describe("User entitlements information."),
177
+ }).describe("Entitlements information.");
@@ -1 +1,19 @@
1
- export {};
1
+ import { z } from "zod";
2
+ export const CanIDeploySchema = z.object({
3
+ pacticipant: z.string().describe("The name of the pacticipant (application/service) being evaluated for deployment"),
4
+ version: z.string().describe("The version of the pacticipant that you want to check if it's safe to deploy"),
5
+ environment: z.string().describe("The target environment where the pacticipant version will be deployed (e.g., 'production', 'staging', 'test')"),
6
+ });
7
+ export const MatrixSchema = z.object({
8
+ latestby: z.string().optional().describe("This property removes the rows for the overridden pacts/verifications from the results. The options are cvp (show only the latest row for each consumer version and provider) and cvpv (show only the latest row each consumer version and provider version). For a can-i-deploy query with one selector, it should be set to cvp. For a can-i-deploy query with two selectors, it should be set to cvpv."),
9
+ limit: z.number().min(1).max(1000).default(100).optional().describe("The limit on the number of results to return (1-1000, default: 100)"),
10
+ q: z.array(z.object({
11
+ pacticipant: z.string().describe("Name of the pacticipant (application)"),
12
+ version: z.string().optional().describe("Version number"),
13
+ branch: z.string().optional().describe("Name of the pacticipant version branch"),
14
+ environment: z.string().optional().describe("The name of the environment that the pacticipant version is deployed to"),
15
+ latest: z.boolean().optional().describe("Used in conjunction with other properties to indicate whether the selector is describing the latest version from a branch/with a tag/for a pacticipant, or all of them. Note that when used with tags, the 'latest' is calculated using the creation date of the pacticipant version, NOT the creation date of the tag."),
16
+ tag: z.string().optional().describe("The name of the pacticipant version tag (superseded by branch and environments)"),
17
+ mainBranch: z.boolean().optional().describe("Whether or not the version(s) described are from the main branch of the pacticipant, as set in the mainBranch property of the pacticipant resource."),
18
+ })).min(1).max(2)
19
+ });
@@ -0,0 +1,89 @@
1
+ import { EndpointMatcherSchema, MatcherRecommendationInputSchema, } from "./ai.js";
2
+ import { OADMatcherPrompt } from "./prompts.js";
3
+ /**
4
+ * Get OpenAPI matcher recommendations using sampling.
5
+ *
6
+ * @param openAPI The OpenAPI document to analyze.
7
+ * @param server The SmartBear MCP server instance.
8
+ * @returns A promise that resolves to the matcher recommendations.
9
+ * @throws Error if unable to parse recommendations.
10
+ */
11
+ export async function getOADMatcherRecommendations(openAPI, server) {
12
+ const matcherResponse = await server.createMessage({
13
+ messages: [
14
+ {
15
+ role: "user",
16
+ content: {
17
+ type: "text",
18
+ text: OADMatcherPrompt.replace("{0}", JSON.stringify(openAPI)),
19
+ },
20
+ },
21
+ ],
22
+ maxTokens: 1000,
23
+ });
24
+ const regex = /```json[c5]?([\s\S]*?)```/i;
25
+ const match = regex.exec(matcherResponse.content.text);
26
+ if (match) {
27
+ const jsonText = match[1].trim();
28
+ const parsed = JSON.parse(jsonText);
29
+ const matcherRecommendations = MatcherRecommendationInputSchema.parse(parsed);
30
+ return matcherRecommendations;
31
+ }
32
+ else {
33
+ throw new Error("Unable to parse recommendations please provide OpenAPI matchers manually.");
34
+ }
35
+ }
36
+ /**
37
+ * Get user selection for matcher recommendations.
38
+ *
39
+ * @param recommendations The list of matcher recommendations.
40
+ * @param getInput The function to get user input.
41
+ * @returns The selected endpoint matcher.
42
+ */
43
+ export async function getUserMatcherSelection(recommendations, getInput) {
44
+ const recommendationsMap = new Map();
45
+ recommendations.forEach((rec, index) => {
46
+ recommendationsMap.set(`Recommendation ${index + 1}`, JSON.stringify(rec));
47
+ });
48
+ const result = await getInput({
49
+ message: `Select one of the generated matchers you would want to use \n\n ${recommendations
50
+ .map((rec, index) => `\n\nRecommendation ${index + 1}: \n\n\n ${JSON.stringify(rec)}`)
51
+ .join("\n\n")}`,
52
+ requestedSchema: {
53
+ type: "object",
54
+ properties: {
55
+ generatedMatchers: {
56
+ type: "string",
57
+ title: "Generated Matchers",
58
+ description: "Use the matchers generated for the OpenAPI document",
59
+ enum: recommendations.map((_, index) => `Recommendation ${index + 1}`),
60
+ },
61
+ },
62
+ required: ["generatedMatchers"],
63
+ },
64
+ });
65
+ if (result.action === "accept") {
66
+ return EndpointMatcherSchema.parse(JSON.parse(recommendationsMap.get(result.content?.generatedMatchers) ||
67
+ ""));
68
+ }
69
+ else {
70
+ const result = await getInput({
71
+ message: "Enter the matchers you would want to use for the OpenAPI document",
72
+ requestedSchema: {
73
+ type: "object",
74
+ properties: {
75
+ enteredMatchers: {
76
+ type: "string",
77
+ title: "Enter the matchers you would want to use for the OpenAPI document",
78
+ description: "Enter the matchers you would want to use for the OpenAPI document",
79
+ },
80
+ },
81
+ required: ["enteredMatchers"],
82
+ },
83
+ });
84
+ if (result.action === "accept") {
85
+ return EndpointMatcherSchema.parse(JSON.parse(result.content?.enteredMatchers));
86
+ }
87
+ return {};
88
+ }
89
+ }
@@ -0,0 +1,133 @@
1
+ import { z } from "zod";
2
+ import { zodToJsonSchema } from "zod-to-json-schema";
3
+ import { EndpointMatcherSchema } from "./ai.js";
4
+ const OADMatcherPromptOpenAPIDocExample = {
5
+ openapi: "3.1.0",
6
+ info: {
7
+ title: "My API",
8
+ version: "1.0.0",
9
+ description: "A sample API for demonstration purposes.",
10
+ },
11
+ paths: {
12
+ "/users": {
13
+ get: {
14
+ summary: "Get all users",
15
+ responses: {
16
+ "200": {
17
+ description: "A list of users",
18
+ content: {
19
+ "application/json": {
20
+ schema: {
21
+ type: "array",
22
+ items: {
23
+ $ref: "#/components/schemas/User",
24
+ },
25
+ },
26
+ },
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ },
33
+ components: {
34
+ schemas: {
35
+ User: {
36
+ type: "object",
37
+ properties: {
38
+ id: {
39
+ type: "integer",
40
+ format: "int64",
41
+ },
42
+ name: {
43
+ type: "string",
44
+ },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ };
50
+ const OADMatcherPromptRecommendationExample = [
51
+ {
52
+ path: "/users",
53
+ methods: ["GET"],
54
+ statusCodes: [200, "2XX"],
55
+ operationId: "get*",
56
+ },
57
+ {
58
+ path: "/users/*",
59
+ methods: ["GET"],
60
+ statusCodes: ["2XX"],
61
+ operationId: "*User*",
62
+ },
63
+ {
64
+ path: "/users",
65
+ methods: ["GET"],
66
+ statusCodes: [200],
67
+ operationId: "getAllUsers",
68
+ },
69
+ {
70
+ path: "/users/**",
71
+ methods: ["GET"],
72
+ statusCodes: ["2XX", 404],
73
+ operationId: "get*",
74
+ },
75
+ ];
76
+ export const OADMatcherPrompt = `
77
+
78
+ Generate a list of recommendations(maximum of 5) in JSON to use with an OpenAPI matcher.
79
+ Zod Schema for the matcher to be generated is provided below in the markdown block of javascript use this to generate the recommendations for the matcher. Recommendations should contain all the fields from the schema and only output the JSON in a markdown formatted block.
80
+
81
+ \`\`\`javascript
82
+ const EndpointMatcherSchema = ${JSON.stringify(zodToJsonSchema(EndpointMatcherSchema))};
83
+ \`\`\`
84
+
85
+ Example OpenAPI document:-
86
+
87
+ if OpenAPI document provided is:-
88
+
89
+ \`\`\`json
90
+ ${JSON.stringify(OADMatcherPromptOpenAPIDocExample, null, 2)}
91
+ \`\`\`
92
+
93
+ Generated recommendations are:-
94
+
95
+ \`\`\`json
96
+ ${JSON.stringify(OADMatcherPromptRecommendationExample, null, 2)}
97
+ \`\`\`
98
+
99
+ Actual OpenAPI document:-
100
+
101
+ Now provided the below OpenAPI document:-
102
+
103
+ \`\`\`json
104
+ {0}
105
+ \`\`\`
106
+
107
+ Give JSON recommendations only provide the JSON block in markdown don't include any additional text.
108
+ `;
109
+ export const PROMPTS = [
110
+ {
111
+ name: "OpenAPI Matcher recommendations",
112
+ params: {
113
+ description: "Get OpenAPI matcher recommendations using sampling",
114
+ title: "OpenAPI Matcher recommendations",
115
+ argsSchema: {
116
+ openAPI: z.string(),
117
+ },
118
+ },
119
+ callback: function ({ openAPI }) {
120
+ return {
121
+ messages: [
122
+ {
123
+ role: "user",
124
+ content: {
125
+ type: "text",
126
+ text: OADMatcherPrompt.replace("{0}", openAPI),
127
+ },
128
+ },
129
+ ],
130
+ };
131
+ },
132
+ },
133
+ ];
@@ -10,7 +10,8 @@
10
10
  * and registered with their corresponding handler.
11
11
  */
12
12
  import { z } from "zod";
13
- import { GenerationInputSchema, RefineInputSchema } from "./ai.js";
13
+ import { GenerationInputSchema, RefineInputSchema, } from "./ai.js";
14
+ import { CanIDeploySchema, MatrixSchema } from "./base.js";
14
15
  export const TOOLS = [
15
16
  {
16
17
  title: "Generate Pact Tests",
@@ -19,6 +20,7 @@ export const TOOLS = [
19
20
  zodSchema: GenerationInputSchema,
20
21
  handler: "generate",
21
22
  clients: ["pactflow"], // ONLY pactflow
23
+ enableElicitation: true,
22
24
  },
23
25
  {
24
26
  title: "Review Pact Tests",
@@ -26,7 +28,8 @@ export const TOOLS = [
26
28
  purpose: "Review Pact tests for API interactions",
27
29
  zodSchema: RefineInputSchema,
28
30
  handler: "review",
29
- clients: ["pactflow"]
31
+ clients: ["pactflow"],
32
+ enableElicitation: true,
30
33
  },
31
34
  {
32
35
  title: "Get Provider States",
@@ -42,5 +45,43 @@ export const TOOLS = [
42
45
  ],
43
46
  handler: "getProviderStates",
44
47
  clients: ["pactflow", "pact_broker"]
48
+ },
49
+ {
50
+ title: "Can I Deploy",
51
+ summary: "Performs a comprehensive compatibility check to determine whether a specific version of a service (pacticipant) can be safely deployed into a given environment. It analyzes the complete contract matrix of consumer-provider relationships to confirm that all required integrations are verified and compatible.",
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
+ zodSchema: CanIDeploySchema,
54
+ handler: "canIDeploy",
55
+ clients: ["pactflow", "pact_broker"]
56
+ },
57
+ {
58
+ title: "Matrix",
59
+ summary: "Retrieve the comprehensive contract verification matrix that shows the relationship between consumer and provider versions, their associated pact files, and verification results stored in the Pact Broker or Pactflow. The matrix provides detailed visibility into which consumer and provider versions have been successfully verified against each other, and highlights failures with detailed information about the cause.",
60
+ purpose: "The Matrix serves as a powerful tool for teams to understand the state of their contract testing ecosystem. It enables tracking of all interactions between consumer and provider versions over time, with detailed insights into verification successes and failures. This helps teams rapidly identify compatibility issues, understand why specific verifications failed, and make informed decisions about deployments. Matrix offers a more intuitive and consolidated view of the verification status, making it easier to spot trends or problematic versions. Additionally, the Matrix supports complex queries using selectors, and can answer specific 'can-i-deploy' questions, ensuring that only compatible versions are deployed to production environments.",
61
+ useCases: [
62
+ "Quickly identify which consumer and provider version combinations have passed or failed verification.",
63
+ "Diagnose and investigate why a particular consumer-provider verification failed.",
64
+ "Visualize the overall contract compatibility across two pacticipants / services.",
65
+ "Perform advanced queries using selectors to understand compatibility within specific branches, environments, or version ranges.",
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."
68
+ ],
69
+ zodSchema: MatrixSchema,
70
+ handler: "getMatrix",
71
+ clients: ["pactflow", "pact_broker"]
72
+ },
73
+ {
74
+ title: "PactFlow AI Status",
75
+ summary: "Check PactFlow AI usage status, remaining credits, and eligibility",
76
+ purpose: "Retrieve the AI feature status for the PactFlow account, including whether AI is enabled, the number of remaining and consumed AI credits, and entitlement or permission issues preventing usage.",
77
+ useCases: [
78
+ "Verify if AI functionality is enabled for the account before attempting to use AI-powered features",
79
+ "Monitor remaining and consumed AI credits to manage usage and avoid unexpected disruptions",
80
+ "Detect entitlement or permission issues when a user tries to access AI features and guide corrective actions",
81
+ "Integrate into deployment pipelines to ensure the environment is correctly configured with necessary entitlements and sufficient credits before executing AI-driven tasks",
82
+ "Fetches usage and entitlement reports for auditing, budgeting, and compliance purposes"
83
+ ],
84
+ handler: "getAIStatus",
85
+ clients: ["pactflow"]
45
86
  }
46
87
  ];
@@ -0,0 +1,70 @@
1
+ import { RemoteOpenAPIDocumentSchema, } from "./ai.js";
2
+ import yaml from "js-yaml";
3
+ // @ts-expect-error missing type declarations
4
+ import Swagger from "swagger-client";
5
+ /**
6
+ * Resolve the OpenAPI specification from the provided input.
7
+ *
8
+ * @param remoteOpenAPIDocument The remote OpenAPI document to resolve.
9
+ * @returns The resolved OpenAPI document.
10
+ * @throws Error if the resolution fails.
11
+ */
12
+ export async function resolveOpenAPISpec(remoteOpenAPIDocument) {
13
+ const openAPISchema = RemoteOpenAPIDocumentSchema.safeParse(remoteOpenAPIDocument);
14
+ if (openAPISchema.error || !remoteOpenAPIDocument) {
15
+ throw new Error(`Invalid RemoteOpenAPIDocument: ${JSON.stringify(openAPISchema.error?.issues)}`);
16
+ }
17
+ const unresolvedSpec = await getRemoteSpecContents(openAPISchema.data);
18
+ const resolvedSpec = await Swagger.resolve({ spec: unresolvedSpec });
19
+ if (resolvedSpec.errors?.length) {
20
+ throw new Error(`Failed to resolve OpenAPI document: ${resolvedSpec.errors?.join(", ")}`);
21
+ }
22
+ return resolvedSpec.spec;
23
+ }
24
+ /**
25
+ * Fetch the contents of a remote OpenAPI document.
26
+ *
27
+ * @param openAPISchema The schema for the remote OpenAPI document.
28
+ * @returns A promise that resolves to a map of the OpenAPI document contents.
29
+ * @throws Error if the URL is not provided or the fetch fails.
30
+ */
31
+ export async function getRemoteSpecContents(openAPISchema) {
32
+ if (!openAPISchema.url) {
33
+ throw new Error("'url' must be provided.");
34
+ }
35
+ let headers = {};
36
+ if (openAPISchema.authToken) {
37
+ headers = {
38
+ Authorization: `${openAPISchema.authScheme ?? "Bearer"} ${openAPISchema.authToken}`,
39
+ };
40
+ }
41
+ const remoteSpec = await fetch(openAPISchema.url, {
42
+ headers,
43
+ method: "GET",
44
+ });
45
+ const specRawBody = await remoteSpec.text();
46
+ try {
47
+ return JSON.parse(specRawBody);
48
+ }
49
+ catch (jsonError) {
50
+ try {
51
+ return yaml.load(specRawBody);
52
+ }
53
+ catch (yamlError) {
54
+ throw new Error(`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
+ }
57
+ }
58
+ /**
59
+ * Adds the OpenAPI specification to the input schema if a remote document is provided.
60
+ *
61
+ * @param inputSchema The input schema to modify.
62
+ * @returns The modified input schema with the OpenAPI specification added.
63
+ */
64
+ export async function addOpenAPISpecToSchema(inputSchema) {
65
+ if (inputSchema.remoteDocument) {
66
+ const resolvedSpec = await resolveOpenAPISpec(inputSchema.remoteDocument);
67
+ inputSchema.document = resolvedSpec;
68
+ }
69
+ return inputSchema;
70
+ }