@smartbear/mcp 0.3.0 → 0.4.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 CHANGED
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  <br />
20
20
 
21
- A Model Context Protocol (MCP) server which provides AI assistants with seamless access to SmartBear's suite of testing and monitoring tools, including [Insight Hub](https://www.smartbear.com/insight-hub), [Reflect](https://reflect.run), and [API Hub](https://www.smartbear.com/api-hub).
21
+ A Model Context Protocol (MCP) server which provides AI assistants with seamless access to SmartBear's suite of testing and monitoring tools, including [Insight Hub](https://www.smartbear.com/insight-hub), [Reflect](https://reflect.run), [API Hub](https://www.smartbear.com/api-hub), [PactFlow](https://pactflow.io/) and [Pact Broker](https://docs.pact.io/)
22
22
 
23
23
  ## What is MCP?
24
24
 
@@ -31,6 +31,7 @@ See individual guides for suggested prompts and supported tools and resources:
31
31
  - [Insight Hub](https://developer.smartbear.com/smartbear-mcp/docs/insight-hub-integration) - Comprehensive error monitoring and debugging capabilities
32
32
  - [Test Hub](https://developer.smartbear.com/smartbear-mcp/docs/test-hub-integration) - Test management and execution capabilities
33
33
  - [API Hub](https://developer.smartbear.com/smartbear-mcp/docs/api-hub-integration) - Portal management capabilities
34
+ - [PactFlow](https://developer.smartbear.com/pactflow/default/getting-started) - Contract testing capabilities
34
35
 
35
36
 
36
37
  ## Prerequisites
@@ -68,7 +69,11 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
68
69
  "INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
69
70
  "INSIGHT_HUB_PROJECT_API_KEY": "${input:insight_hub_project_api_key}",
70
71
  "REFLECT_API_TOKEN": "${input:reflect_api_token}",
71
- "API_HUB_API_KEY": "${input:api_hub_api_key}"
72
+ "API_HUB_API_KEY": "${input:api_hub_api_key}",
73
+ "PACT_BROKER_BASE_URL": "${input:pact_broker_base_url}",
74
+ "PACT_BROKER_TOKEN": "${input:pact_broker_token}",
75
+ "PACT_BROKER_USERNAME": "${input:pact_broker_username}",
76
+ "PACT_BROKER_PASSWORD": "${input:pact_broker_password}"
72
77
  }
73
78
  }
74
79
  },
@@ -96,7 +101,31 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
96
101
  "type": "promptString",
97
102
  "description": "API Hub API Key - leave blank to disable API Hub tools",
98
103
  "password": true
99
- }
104
+ },
105
+ {
106
+ "id": "pact_broker_base_url",
107
+ "type": "promptString",
108
+ "description": "PactFlow or Pact Broker base url - leave blank to disable the tools",
109
+ "password": true
110
+ },
111
+ {
112
+ "id": "pact_broker_token",
113
+ "type": "promptString",
114
+ "description": "PactFlow Authentication Token",
115
+ "password": true
116
+ },
117
+ {
118
+ "id": "pact_broker_username",
119
+ "type": "promptString",
120
+ "description": "Pact Broker Username",
121
+ "password": true
122
+ },
123
+ {
124
+ "id": "pact_broker_password",
125
+ "type": "promptString",
126
+ "description": "Pact Broker Password",
127
+ "password": true
128
+ },
100
129
  ]
101
130
  }
102
131
  ```
@@ -119,7 +148,11 @@ Add the following configuration to your `claude_desktop_config.json` to launch t
119
148
  "INSIGHT_HUB_AUTH_TOKEN": "your_personal_auth_token",
120
149
  "INSIGHT_HUB_PROJECT_API_KEY": "your_project_api_key",
121
150
  "REFLECT_API_TOKEN": "your_reflect_token",
122
- "API_HUB_API_KEY": "your_api_hub_key"
151
+ "API_HUB_API_KEY": "your_api_hub_key",
152
+ "PACT_BROKER_BASE_URL": "your_pactflow_or_pactbroker_base_url",
153
+ "PACT_BROKER_TOKEN": "your_pactflow_token",
154
+ "PACT_BROKER_USERNAME": "your_pact_broker_username",
155
+ "PACT_BROKER_PASSWORD": "your_pact_broker_password",
123
156
  }
124
157
  }
125
158
  }
@@ -172,7 +205,11 @@ For developers who want to contribute to the SmartBear MCP server, customize its
172
205
  "INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
173
206
  "INSIGHT_HUB_PROJECT_API_KEY": "${input:insight_hub_project_api_key}",
174
207
  "REFLECT_API_TOKEN": "${input:reflect_api_token}",
175
- "API_HUB_API_KEY": "${input:api_hub_api_key}"
208
+ "API_HUB_API_KEY": "${input:api_hub_api_key}",
209
+ "PACT_BROKER_BASE_URL": "${input:pact_broker_base_url}",
210
+ "PACT_BROKER_TOKEN": "${input:pact_broker_token}",
211
+ "PACT_BROKER_USERNAME": "${input:pact_broker_username}",
212
+ "PACT_BROKER_PASSWORD": "${input:pact_broker_password}"
176
213
  }
177
214
  }
178
215
  },
@@ -200,7 +237,31 @@ For developers who want to contribute to the SmartBear MCP server, customize its
200
237
  "type": "promptString",
201
238
  "description": "API Hub API Key - leave blank to disable API Hub tools",
202
239
  "password": true
203
- }
240
+ },
241
+ {
242
+ "id": "pact_broker_base_url",
243
+ "type": "promptString",
244
+ "description": "PactFlow or Pact Broker base url - leave blank to disable PactFlow tools",
245
+ "password": true
246
+ },
247
+ {
248
+ "id": "pact_broker_token",
249
+ "type": "promptString",
250
+ "description": "PactFlow Authentication Token",
251
+ "password": true
252
+ },
253
+ {
254
+ "id": "pact_broker_username",
255
+ "type": "promptString",
256
+ "description": "Pact Broker Username",
257
+ "password": true
258
+ },
259
+ {
260
+ "id": "pact_broker_password",
261
+ "type": "promptString",
262
+ "description": "Pact Broker Password",
263
+ "password": true
264
+ },
204
265
  ]
205
266
  }
206
267
  ```
@@ -209,7 +270,7 @@ For developers who want to contribute to the SmartBear MCP server, customize its
209
270
  5. **Local testing** using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) web interface:
210
271
 
211
272
  ```bash
212
- INSIGHT_HUB_AUTH_TOKEN="your_token" REFLECT_API_TOKEN="your_reflect_token" API_HUB_API_KEY="your_api_hub_key" npx @modelcontextprotocol/inspector npx @smartbear/mcp@latest
273
+ INSIGHT_HUB_AUTH_TOKEN="your_token" REFLECT_API_TOKEN="your_reflect_token" API_HUB_API_KEY="your_api_hub_key" PACT_BROKER_BASE_URL="your-pactflow-url" PACT_BROKER_TOKEN="your-pactflow-token" PACT_BROKER_USERNAME="your-pact-broker-username" PACT_BROKER_PASSWORD="your-pact-broker-password" npx @modelcontextprotocol/inspector npx @smartbear/mcp@latest
213
274
  ```
214
275
 
215
276
  ## License
@@ -3,6 +3,8 @@ import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
3
3
  // Tool definitions for API Hub API client
4
4
  export class ApiHubClient {
5
5
  headers;
6
+ name = "API Hub";
7
+ prefix = "api_hub";
6
8
  constructor(token) {
7
9
  this.headers = {
8
10
  "Authorization": `Bearer ${token}`,
@@ -82,99 +84,343 @@ export class ApiHubClient {
82
84
  });
83
85
  return response.json();
84
86
  }
85
- registerTools(server) {
86
- server.tool("list_portals", "Search for available portals within API Hub. Only portals where you have at least a designer role, either at the product level or organization level, are returned.", {}, async (_args, _extra) => {
87
+ registerTools(register, _getInput) {
88
+ register({
89
+ title: "List Portals",
90
+ summary: "Search for available portals within API Hub. Only portals where you have at least a designer role, either at the product level or organization level, are returned.",
91
+ parameters: [],
92
+ }, async (_args, _extra) => {
87
93
  const response = await this.getPortals();
88
94
  return {
89
95
  content: [{ type: "text", text: JSON.stringify(response) }],
90
96
  };
91
97
  });
92
- server.tool("create_portal", "Create a new portal within API Hub.", {
93
- name: z.string().optional().describe("The portal name."),
94
- subdomain: z.string().describe("The portal subdomain."),
95
- offline: z.boolean().optional().describe("If set to true the portal will not be visible to customers."),
96
- routing: z.string().optional().describe("Determines the routing strategy ('browser' or 'proxy')."),
97
- credentialsEnabled: z.string().optional().describe("Indicates if credentials are enabled for the portal."),
98
- swaggerHubOrganizationId: z.string().describe("The corresponding API Hub (formerly SwaggerHub) organization UUID."),
99
- openapiRenderer: z.string().optional().describe("Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default."),
100
- pageContentFormat: z.string().optional().describe("The format of the page content.")
98
+ register({
99
+ title: "Create Portal",
100
+ summary: "Create a new portal within API Hub.",
101
+ parameters: [
102
+ {
103
+ name: "name",
104
+ type: z.string(),
105
+ required: false,
106
+ description: "The portal name.",
107
+ },
108
+ {
109
+ name: "subdomain",
110
+ type: z.string(),
111
+ required: true,
112
+ description: "The portal subdomain.",
113
+ },
114
+ {
115
+ name: "offline",
116
+ type: z.boolean(),
117
+ required: false,
118
+ description: "If set to true the portal will not be visible to customers.",
119
+ },
120
+ {
121
+ name: "routing",
122
+ type: z.string(),
123
+ required: false,
124
+ description: "Determines the routing strategy ('browser' or 'proxy').",
125
+ },
126
+ {
127
+ name: "credentialsEnabled",
128
+ type: z.string(),
129
+ required: false,
130
+ description: "Indicates if credentials are enabled for the portal.",
131
+ },
132
+ {
133
+ name: "swaggerHubOrganizationId",
134
+ type: z.string(),
135
+ required: true,
136
+ description: "The corresponding API Hub (formerly SwaggerHub) organization UUID.",
137
+ },
138
+ {
139
+ name: "openapiRenderer",
140
+ type: z.string(),
141
+ required: false,
142
+ description: "Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default.",
143
+ },
144
+ {
145
+ name: "pageContentFormat",
146
+ type: z.string(),
147
+ required: false,
148
+ description: "The format of the page content.",
149
+ }
150
+ ],
101
151
  }, async (args, _extra) => {
102
- const response = await this.createPortal(args);
152
+ const createPortalArgs = args;
153
+ const response = await this.createPortal(createPortalArgs);
103
154
  return {
104
155
  content: [{ type: "text", text: JSON.stringify(response) }],
105
156
  };
106
157
  });
107
- server.tool("get_portal", "Retrieve information about a specific portal.", { portalId: z.string().describe("Portal UUID or subdomain.") }, async (args, _extra) => {
108
- const response = await this.getPortal(args.portalId);
158
+ register({
159
+ title: "Get Portal",
160
+ summary: "Retrieve information about a specific portal.",
161
+ parameters: [
162
+ {
163
+ name: "portalId",
164
+ type: z.string(),
165
+ description: "Portal UUID or subdomain.",
166
+ required: true
167
+ },
168
+ ],
169
+ }, async (args, _extra) => {
170
+ const portalArgs = args;
171
+ const response = await this.getPortal(portalArgs.portalId);
109
172
  return {
110
173
  content: [{ type: "text", text: JSON.stringify(response) }],
111
174
  };
112
175
  });
113
- server.tool("delete_portal", "Delete a portal.", { portalId: z.string().describe("Portal UUID or subdomain.") }, async (args, _extra) => {
114
- await this.deletePortal(args.portalId);
176
+ register({
177
+ title: "Delete Portal",
178
+ summary: "Delete a specific portal.",
179
+ parameters: [
180
+ {
181
+ name: "portalId",
182
+ type: z.string(),
183
+ description: "Portal UUID or subdomain.",
184
+ required: true
185
+ }
186
+ ],
187
+ }, async (args, _extra) => {
188
+ const portalArgs = args;
189
+ await this.deletePortal(portalArgs.portalId);
115
190
  return {
116
191
  content: [{ type: "text", text: "Portal deleted successfully." }],
117
192
  };
118
193
  });
119
- server.tool("update_portal", "Update a specific portal's configuration", {
120
- portalId: z.string().describe("Portal UUID or subdomain."),
121
- name: z.string().optional().describe("The portal name."),
122
- subdomain: z.string().optional().describe("The portal subdomain."),
123
- customDomain: z.boolean().optional().describe("Indicates if the portal has a custom domain."),
124
- gtmKey: z.string().optional().describe("Google Tag Manager key for the portal."),
125
- offline: z.boolean().optional().describe("If set to true the portal will not be visible to customers."),
126
- routing: z.string().optional().describe("Determines the routing strategy ('browser' or 'proxy')."),
127
- credentialsEnabled: z.boolean().optional().describe("Indicates if credentials are enabled for the portal."),
128
- openapiRenderer: z.string().optional().describe("Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default."),
129
- pageContentFormat: z.string().optional().describe("The format of the page content.")
194
+ register({
195
+ title: "Update Portal",
196
+ summary: "Update a specific portal's configuration.",
197
+ parameters: [
198
+ {
199
+ name: "portalId",
200
+ type: z.string(),
201
+ description: "Portal UUID or subdomain.",
202
+ required: true
203
+ },
204
+ {
205
+ name: "name",
206
+ type: z.string(),
207
+ description: "The portal name.",
208
+ required: false
209
+ },
210
+ {
211
+ name: "subdomain",
212
+ type: z.string(),
213
+ description: "The portal subdomain.",
214
+ required: false
215
+ },
216
+ {
217
+ name: "customDomain",
218
+ type: z.boolean(),
219
+ description: "Indicates if the portal has a custom domain.",
220
+ required: false
221
+ },
222
+ {
223
+ name: "gtmKey",
224
+ type: z.string(),
225
+ description: "Google Tag Manager key for the portal.",
226
+ required: false
227
+ },
228
+ {
229
+ name: "offline",
230
+ type: z.boolean(),
231
+ description: "If set to true the portal will not be visible to customers.",
232
+ required: false
233
+ },
234
+ {
235
+ name: "routing",
236
+ type: z.string(),
237
+ description: "Determines the routing strategy ('browser' or 'proxy').",
238
+ required: false
239
+ },
240
+ {
241
+ name: "credentialsEnabled",
242
+ type: z.boolean(),
243
+ description: "Indicates if credentials are enabled for the portal.",
244
+ required: false
245
+ },
246
+ {
247
+ name: "openapiRenderer",
248
+ type: z.string(),
249
+ description: "Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default.",
250
+ required: false
251
+ },
252
+ {
253
+ name: "pageContentFormat",
254
+ type: z.string(),
255
+ description: "The format of the page content.",
256
+ required: false
257
+ }
258
+ ],
130
259
  }, async (args, _extra) => {
131
- const response = await this.updatePortal(args.portalId, args);
260
+ const updatePortalArgs = args;
261
+ const response = await this.updatePortal(updatePortalArgs.portalId, updatePortalArgs);
132
262
  return {
133
263
  content: [{ type: "text", text: JSON.stringify(response) }],
134
264
  };
135
265
  });
136
- server.tool("list_portal_products", "Get products for a specific portal that match your criteria.", { portalId: z.string().describe("Portal UUID or subdomain.") }, async (args, _extra) => {
137
- const response = await this.getPortalProducts(args.portalId);
266
+ register({
267
+ title: "List Portal Products",
268
+ summary: "Get products for a specific portal that match your criteria.",
269
+ parameters: [
270
+ {
271
+ name: "portalId",
272
+ type: z.string(),
273
+ description: "Portal UUID or subdomain.",
274
+ required: true
275
+ }
276
+ ],
277
+ }, async (args, _extra) => {
278
+ const portalArgs = args;
279
+ const response = await this.getPortalProducts(portalArgs.portalId);
138
280
  return {
139
281
  content: [{ type: "text", text: JSON.stringify(response) }],
140
282
  };
141
283
  });
142
- server.tool("create_portal_product", "Create a new product for a specific portal.", {
143
- portalId: z.string().describe("Portal UUID or subdomain."),
144
- type: z.string().describe("Product type (Allowed values: 'new', 'copy')."),
145
- name: z.string().describe("Product name."),
146
- slug: z.string().describe("URL component for this product. Must be unique within the portal."),
147
- description: z.string().optional().describe("Product description."),
148
- public: z.boolean().optional().describe("Indicates if the product is public."),
149
- hidden: z.string().optional().describe("Indicates if the product is hidden."),
150
- role: z.boolean().optional().describe("Indicates if the product has a role.")
284
+ register({
285
+ title: "Create Portal Product",
286
+ summary: "Create a new product for a specific portal.",
287
+ parameters: [
288
+ {
289
+ name: "portalId",
290
+ type: z.string(),
291
+ description: "Portal UUID or subdomain.",
292
+ required: true
293
+ },
294
+ {
295
+ name: "type",
296
+ type: z.string(),
297
+ description: "Product type (Allowed values: 'new', 'copy').",
298
+ required: true
299
+ },
300
+ {
301
+ name: "name",
302
+ type: z.string(),
303
+ description: "Product name.",
304
+ required: true
305
+ },
306
+ {
307
+ name: "slug",
308
+ type: z.string(),
309
+ description: "URL component for this product. Must be unique within the portal.",
310
+ required: true
311
+ },
312
+ {
313
+ name: "description",
314
+ type: z.string(),
315
+ description: "Product description.",
316
+ required: false
317
+ },
318
+ {
319
+ name: "public",
320
+ type: z.boolean(),
321
+ description: "Indicates if the product is public.",
322
+ required: false
323
+ },
324
+ {
325
+ name: "hidden",
326
+ type: z.string(),
327
+ description: "Indicates if the product is hidden.",
328
+ required: false
329
+ },
330
+ {
331
+ name: "role",
332
+ type: z.boolean(),
333
+ description: "Indicates if the product has a role.",
334
+ required: false
335
+ }
336
+ ],
151
337
  }, async (args, _extra) => {
152
- const response = await this.createPortalProduct(args.portalId, args);
338
+ const createProductArgs = args;
339
+ const response = await this.createPortalProduct(createProductArgs.portalId, createProductArgs);
153
340
  return {
154
341
  content: [{ type: "text", text: JSON.stringify(response) }],
155
342
  };
156
343
  });
157
- server.tool("get_portal_product", "Retrieve information about a specific product resource.", { productId: z.string().describe("Product UUID, or identifier in the format.") }, async (args, _extra) => {
158
- const response = await this.getPortalProduct(args.productId);
344
+ register({
345
+ title: "Get Portal Product",
346
+ summary: "Retrieve information about a specific product resource.",
347
+ parameters: [
348
+ {
349
+ name: "productId",
350
+ type: z.string(),
351
+ description: "Product UUID, or identifier in the format.",
352
+ required: true
353
+ }
354
+ ],
355
+ }, async (args, _extra) => {
356
+ const productArgs = args;
357
+ const response = await this.getPortalProduct(productArgs.productId);
159
358
  return {
160
359
  content: [{ type: "text", text: JSON.stringify(response) }],
161
360
  };
162
361
  });
163
- server.tool("delete_portal_product", "Delete a product from a specific portal", { productId: z.string().describe("Product UUID, or identifier in the format.") }, async (args, _extra) => {
164
- await this.deletePortalProduct(args.productId);
362
+ register({
363
+ title: "Delete Portal Product",
364
+ summary: "Delete a product from a specific portal",
365
+ parameters: [
366
+ {
367
+ name: "productId",
368
+ type: z.string(),
369
+ description: "Product UUID, or identifier in the format.",
370
+ required: true
371
+ }
372
+ ],
373
+ }, async (args, _extra) => {
374
+ const productArgs = args;
375
+ await this.deletePortalProduct(productArgs.productId);
165
376
  return {
166
377
  content: [{ type: "text", text: "Product deleted successfully." }],
167
378
  };
168
379
  });
169
- server.tool("update_portal_product", "Update a product's settings within a specific portal.", {
170
- productId: z.string().describe("Product UUID, or identifier in the format."),
171
- name: z.string().optional().describe("Product name."),
172
- slug: z.string().optional().describe("URL component for this product. Must be unique within the portal."),
173
- description: z.string().optional().describe("Product description."),
174
- public: z.boolean().optional().describe("Indicates if the product is public."),
175
- hidden: z.string().optional().describe("Indicates if the product is hidden.")
380
+ register({
381
+ title: "Update Portal Product",
382
+ summary: "Update a product's settings within a specific portal.",
383
+ parameters: [
384
+ {
385
+ name: "productId",
386
+ type: z.string(),
387
+ description: "Product UUID, or identifier in the format.",
388
+ required: true
389
+ },
390
+ {
391
+ name: "name",
392
+ type: z.string(),
393
+ description: "Product name.",
394
+ required: false
395
+ },
396
+ {
397
+ name: "slug",
398
+ type: z.string(),
399
+ description: "URL component for this product. Must be unique within the portal.",
400
+ required: false
401
+ },
402
+ {
403
+ name: "description",
404
+ type: z.string(),
405
+ description: "Product description.",
406
+ required: false
407
+ },
408
+ {
409
+ name: "public",
410
+ type: z.boolean(),
411
+ description: "Indicates if the product is public.",
412
+ required: false
413
+ },
414
+ {
415
+ name: "hidden",
416
+ type: z.string(),
417
+ description: "Indicates if the product is hidden.",
418
+ required: false
419
+ }
420
+ ],
176
421
  }, async (args, _extra) => {
177
- const response = await this.updatePortalProduct(args.productId, args);
422
+ const updateProductArgs = args;
423
+ const response = await this.updatePortalProduct(updateProductArgs.productId, updateProductArgs);
178
424
  return {
179
425
  content: [{ type: "text", text: JSON.stringify(response) }],
180
426
  };