@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,5 +1,7 @@
1
- import { TOOLS } from "./client/tools.js";
2
1
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
2
+ import { TOOLS } from "./client/tools.js";
3
+ import { getOADMatcherRecommendations, getUserMatcherSelection } from "./client/prompt-utils.js";
4
+ import { PROMPTS } from "./client/prompts.js";
3
5
  // Tool definitions for PactFlow AI API client
4
6
  export class PactflowClient {
5
7
  name = "Contract Testing";
@@ -8,7 +10,16 @@ export class PactflowClient {
8
10
  aiBaseUrl;
9
11
  baseUrl;
10
12
  clientType;
11
- constructor(auth, baseUrl, clientType) {
13
+ server;
14
+ /**
15
+ * Creates an instance of the PactflowClient.
16
+ *
17
+ * @param auth The authentication token or credentials.
18
+ * @param baseUrl The base URL for the API.
19
+ * @param clientType The type of client (e.g., PactFlow, Pact Broker).
20
+ * @param server The SmartBear MCP server instance.
21
+ */
22
+ constructor(auth, baseUrl, clientType, server) {
12
23
  // Set headers based on the type of auth provided
13
24
  if (typeof auth === "string") {
14
25
  this.headers = {
@@ -28,34 +39,87 @@ export class PactflowClient {
28
39
  this.baseUrl = baseUrl;
29
40
  this.aiBaseUrl = `${this.baseUrl}/api/ai`;
30
41
  this.clientType = clientType;
42
+ this.server = server;
31
43
  }
32
44
  // PactFlow AI client methods
33
- async generate(body) {
45
+ /**
46
+ * Generate new Pact tests based on the provided input.
47
+ *
48
+ * @param toolInput The input data for the generation process.
49
+ * @param getInput Function to get additional input from the user if needed.
50
+ * @returns The result of the generation process.
51
+ * @throws Error if the HTTP request fails or the operation times out.
52
+ */
53
+ async generate(toolInput, getInput) {
54
+ if (toolInput.openapi?.document && (!toolInput.openapi?.matcher || Object.keys(toolInput.openapi.matcher).length === 0)) {
55
+ const matcherResponse = await getOADMatcherRecommendations(toolInput.openapi.document, this.server);
56
+ const userSelection = await getUserMatcherSelection(matcherResponse, getInput);
57
+ toolInput.openapi.matcher = userSelection;
58
+ }
34
59
  // Submit the generation request
35
60
  const response = await fetch(`${this.aiBaseUrl}/generate`, {
36
61
  method: "POST",
37
62
  headers: this.headers,
38
- body: JSON.stringify(body),
63
+ body: JSON.stringify(toolInput),
39
64
  });
40
65
  if (!response.ok) {
41
- throw new Error(`HTTP error! status: ${response.status}`);
66
+ throw new Error(`HTTP error! status: ${response.status} - ${await response.text()}`);
42
67
  }
43
68
  const status_response = await response.json();
44
69
  return await this.pollForCompletion(status_response, "Generation");
45
70
  }
46
- async review(body) {
47
- // submit review request
71
+ /**
72
+ * Review the provided Pact tests and suggest improvements.
73
+ *
74
+ * @param toolInput The input data for the review process.
75
+ * @param getInput Function to get additional input from the user if needed.
76
+ * @returns The result of the review process.
77
+ * @throws Error if the HTTP request fails or the operation times out.
78
+ */
79
+ async review(toolInput, getInput) {
80
+ if (toolInput.openapi?.document && (!toolInput.openapi?.matcher || Object.keys(toolInput.openapi.matcher).length === 0)) {
81
+ const matcherResponse = await getOADMatcherRecommendations(toolInput.openapi.document, this.server);
82
+ const userSelection = await getUserMatcherSelection(matcherResponse, getInput);
83
+ toolInput.openapi.matcher = userSelection;
84
+ }
85
+ // Submit review request
48
86
  const response = await fetch(`${this.aiBaseUrl}/review`, {
49
87
  method: "POST",
50
88
  headers: this.headers,
51
- body: JSON.stringify(body),
89
+ body: JSON.stringify(toolInput),
52
90
  });
53
91
  if (!response.ok) {
54
- throw new Error(`HTTP error! status: ${response.status}`);
92
+ throw new Error(`HTTP error! status: ${response.status} - ${await response.text()}`);
55
93
  }
56
94
  const status_response = await response.json();
57
95
  return await this.pollForCompletion(status_response, "Review Pacts");
58
96
  }
97
+ /**
98
+ * Retrieves AI status information for the current user
99
+ * and organization.
100
+ *
101
+ * @returns Entitlement containing AI status information, organization
102
+ * entitlements, and user entitlements.
103
+ * @throws Error if the request fails or returns a non-OK response.
104
+ */
105
+ async getAIStatus() {
106
+ const url = `${this.aiBaseUrl}/entitlement`;
107
+ try {
108
+ const response = await fetch(url, {
109
+ method: "GET",
110
+ headers: this.headers,
111
+ });
112
+ if (!response.ok) {
113
+ const errorText = await response.text().catch(() => "");
114
+ throw new Error(`PactFlow AI Status Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
115
+ }
116
+ return (await response.json());
117
+ }
118
+ catch (error) {
119
+ process.stderr.write(`[GetAICredits] Unexpected error: ${error}\n`);
120
+ throw error;
121
+ }
122
+ }
59
123
  async getStatus(statusUrl) {
60
124
  const response = await fetch(statusUrl, {
61
125
  method: "HEAD",
@@ -108,16 +172,121 @@ export class PactflowClient {
108
172
  }
109
173
  return response.json();
110
174
  }
111
- registerTools(register, _getInput) {
175
+ /**
176
+ * Checks if a given pacticipant version is safe to deploy
177
+ * to a specified environment.
178
+ *
179
+ * @param body - Input containing:
180
+ * - `pacticipant`: The name of the service (pacticipant).
181
+ * - `version`: The version of the pacticipant being evaluated for deployment.
182
+ * - `environment`: The target environment (e.g., staging, production).
183
+ * @returns CanIDeployResponse containing deployment decision and verification results.
184
+ * @throws Error if the request fails or returns a non-OK response.
185
+ */
186
+ async canIDeploy(body) {
187
+ const { pacticipant, version, environment } = body;
188
+ const queryParams = new URLSearchParams({
189
+ pacticipant,
190
+ version,
191
+ environment,
192
+ });
193
+ const url = `${this.baseUrl}/can-i-deploy?${queryParams.toString()}`;
194
+ try {
195
+ const response = await fetch(url, {
196
+ method: "GET",
197
+ headers: this.headers,
198
+ });
199
+ if (!response.ok) {
200
+ const errorText = await response.text().catch(() => "");
201
+ throw new Error(`Can-I-Deploy Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
202
+ }
203
+ return (await response.json());
204
+ }
205
+ catch (error) {
206
+ console.error(`[CanIDeploy] Unexpected error: ${error}\n`);
207
+ throw error;
208
+ }
209
+ }
210
+ /**
211
+ * Retrieves the matrix of pact verification results for the specified pacticipants.
212
+ * This allows you to see which consumer/provider combinations have been verified
213
+ * and make deployment decisions based on contract test results.
214
+ *
215
+ * @param body - Matrix query parameters including pacticipants, versions, environments, etc.
216
+ * @returns MatrixResponse containing the verification matrix, notices, and summary
217
+ * @throws Error if the request fails or returns a non-OK response
218
+ */
219
+ async getMatrix(body) {
220
+ const { q, latestby, limit } = body;
221
+ // Build query parameters manually to avoid URL encoding of square brackets
222
+ const queryParts = [];
223
+ // Add optional parameters
224
+ if (latestby) {
225
+ queryParts.push(`latestby=${encodeURIComponent(latestby)}`);
226
+ }
227
+ if (limit !== undefined) {
228
+ queryParts.push(`limit=${limit}`);
229
+ }
230
+ // Add the q parameters (pacticipant selectors)
231
+ q.forEach((selector) => {
232
+ queryParts.push(`q[]pacticipant=${encodeURIComponent(selector.pacticipant)}`);
233
+ if (selector.version) {
234
+ queryParts.push(`q[]version=${encodeURIComponent(selector.version)}`);
235
+ }
236
+ if (selector.branch) {
237
+ queryParts.push(`q[]branch=${encodeURIComponent(selector.branch)}`);
238
+ }
239
+ if (selector.environment) {
240
+ queryParts.push(`q[]environment=${encodeURIComponent(selector.environment)}`);
241
+ }
242
+ if (selector.latest !== undefined) {
243
+ queryParts.push(`q[]latest=${selector.latest}`);
244
+ }
245
+ if (selector.tag) {
246
+ queryParts.push(`q[]tag=${encodeURIComponent(selector.tag)}`);
247
+ }
248
+ if (selector.mainBranch !== undefined) {
249
+ queryParts.push(`q[]mainBranch=${selector.mainBranch}`);
250
+ }
251
+ });
252
+ const url = `${this.baseUrl}/matrix?${queryParts.join('&')}`;
253
+ try {
254
+ const response = await fetch(url, {
255
+ method: "GET",
256
+ headers: this.headers,
257
+ });
258
+ if (!response.ok) {
259
+ const errorText = await response.text().catch(() => "");
260
+ throw new Error(`Matrix Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
261
+ }
262
+ return (await response.json());
263
+ }
264
+ catch (error) {
265
+ console.error("[GetMatrix] Unexpected error:", error);
266
+ throw error;
267
+ }
268
+ }
269
+ /**
270
+ * Registers tools with the provided register function.
271
+ *
272
+ * @param register - The function used to register tools.
273
+ * @param getInput - The function used to get input for tools.
274
+ */
275
+ registerTools(register, getInput) {
112
276
  for (const tool of TOOLS.filter(t => t.clients.includes(this.clientType))) {
113
- const { handler, clients, formatResponse, ...toolparams } = tool;
114
- console.log(clients);
277
+ const { handler, clients: _, formatResponse, ...toolparams } = tool; // eslint-disable-line @typescript-eslint/no-unused-vars
115
278
  register(toolparams, async (args, _extra) => {
116
279
  const handler_fn = this[handler];
117
280
  if (typeof handler_fn !== "function") {
118
281
  throw new Error(`Handler '${handler}' not found on PactClient`);
119
282
  }
120
- const result = await handler_fn.call(this, args);
283
+ let result;
284
+ if (tool.enableElicitation) {
285
+ result = await handler_fn.call(this, args, getInput);
286
+ }
287
+ else {
288
+ result = await handler_fn.call(this, args);
289
+ }
121
290
  // Use custom response formatter if provided
122
291
  if (formatResponse) {
123
292
  return formatResponse(result);
@@ -129,4 +298,14 @@ export class PactflowClient {
129
298
  });
130
299
  }
131
300
  }
301
+ /**
302
+ * Registers prompts with the provided register function.
303
+ *
304
+ * @param register - The function used to register prompts.
305
+ */
306
+ registerPrompts(register) {
307
+ PROMPTS.forEach(prompt => {
308
+ register(prompt.name, prompt.params, prompt.callback);
309
+ });
310
+ }
132
311
  }
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@smartbear/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for interacting SmartBear Products",
5
5
  "keywords": [
6
6
  "smartbear",
7
7
  "mcp",
8
- "insight-hub",
8
+ "bugsnag",
9
9
  "reflect",
10
10
  "api-hub",
11
11
  "pactflow"
12
12
  ],
13
13
  "homepage": "https://developer.smartbear.com/smartbear-mcp",
14
+ "mcpName": "com.smartbear/smartbear-mcp",
14
15
  "repository": {
15
16
  "type": "git",
16
17
  "url": "git@github.com:SmartBear/smartbear-mcp.git"
@@ -44,10 +45,13 @@
44
45
  "@bugsnag/js": "^8.2.0",
45
46
  "@modelcontextprotocol/sdk": "^1.15.0",
46
47
  "node-cache": "^5.1.2",
47
- "zod": "^3"
48
+ "swagger-client": "^3.35.6",
49
+ "zod": "^3",
50
+ "zod-to-json-schema": "^3.24.6"
48
51
  },
49
52
  "devDependencies": {
50
53
  "@eslint/js": "^9.29.0",
54
+ "@types/js-yaml": "^4.0.9",
51
55
  "@types/node": "^22",
52
56
  "@vitest/coverage-v8": "^3.2.4",
53
57
  "eslint": "^9.29.0",
@@ -55,6 +59,7 @@
55
59
  "shx": "^0.3.4",
56
60
  "typescript": "^5.6.2",
57
61
  "typescript-eslint": "^8.34.1",
58
- "vitest": "^3.2.4"
62
+ "vitest": "^3.2.4",
63
+ "vitest-fetch-mock": "^0.4.5"
59
64
  }
60
65
  }
@@ -1,46 +0,0 @@
1
- import { BaseAPI, pickFieldsFromArray } from "./base.js";
2
- // --- API Class ---
3
- export class ProjectAPI extends BaseAPI {
4
- static eventFieldFields = [
5
- 'custom',
6
- 'display_id',
7
- 'filter_options',
8
- 'pivot_options'
9
- ];
10
- constructor(configuration) {
11
- super(configuration);
12
- }
13
- /**
14
- * List the Event Fields for a Project
15
- * GET /projects/{project_id}/event_fields
16
- * @param projectId The project ID
17
- * @returns A promise that resolves to the list of event fields
18
- */
19
- async listProjectEventFields(projectId) {
20
- const url = `/projects/${projectId}/event_fields`;
21
- const data = await this.request({
22
- method: 'GET',
23
- url,
24
- });
25
- // Only return allowed fields
26
- return {
27
- ...data,
28
- body: pickFieldsFromArray(data.body || [], ProjectAPI.eventFieldFields)
29
- };
30
- }
31
- /**
32
- * Create a Project in an Organization
33
- * POST /organizations/{organization_id}/projects
34
- * @param organizationId The organization ID
35
- * @param data The project creation request body
36
- * @returns A promise that resolves to the created project
37
- */
38
- async createProject(organizationId, data) {
39
- const url = `/organizations/${organizationId}/projects`;
40
- return await this.request({
41
- method: 'POST',
42
- url,
43
- body: data,
44
- });
45
- }
46
- }
package/dist/package.json DELETED
@@ -1,60 +0,0 @@
1
- {
2
- "name": "@smartbear/mcp",
3
- "version": "0.4.0",
4
- "description": "MCP server for interacting SmartBear Products",
5
- "keywords": [
6
- "smartbear",
7
- "mcp",
8
- "insight-hub",
9
- "reflect",
10
- "api-hub",
11
- "pactflow"
12
- ],
13
- "homepage": "https://developer.smartbear.com/smartbear-mcp",
14
- "repository": {
15
- "type": "git",
16
- "url": "git@github.com:SmartBear/smartbear-mcp.git"
17
- },
18
- "license": "MIT",
19
- "type": "module",
20
- "bin": {
21
- "mcp": "dist/index.js"
22
- },
23
- "files": [
24
- "dist",
25
- "assets",
26
- "**/README.md"
27
- ],
28
- "config": {
29
- "mcpServerName": "SmartBear MCP Server"
30
- },
31
- "scripts": {
32
- "build": "tsc && shx chmod +x dist/*.js",
33
- "lint": "eslint . --ext .ts",
34
- "prepare": "npm run build",
35
- "watch": "tsc --watch",
36
- "test": "vitest",
37
- "test:watch": "vitest --watch",
38
- "test:coverage": "vitest --coverage",
39
- "test:coverage:ci": "vitest --coverage --reporter=verbose",
40
- "test:run": "vitest run",
41
- "coverage:check": "vitest --coverage --reporter=verbose --config vitest.config.coverage.ts"
42
- },
43
- "dependencies": {
44
- "@bugsnag/js": "^8.2.0",
45
- "@modelcontextprotocol/sdk": "^1.15.0",
46
- "node-cache": "^5.1.2",
47
- "zod": "^3"
48
- },
49
- "devDependencies": {
50
- "@eslint/js": "^9.29.0",
51
- "@types/node": "^22",
52
- "@vitest/coverage-v8": "^3.2.4",
53
- "eslint": "^9.29.0",
54
- "globals": "^16.2.0",
55
- "shx": "^0.3.4",
56
- "typescript": "^5.6.2",
57
- "typescript-eslint": "^8.34.1",
58
- "vitest": "^3.2.4"
59
- }
60
- }