@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.
Files changed (41) hide show
  1. package/README.md +37 -3
  2. package/dist/api-hub/client/api.js +387 -0
  3. package/dist/api-hub/client/configuration.js +27 -0
  4. package/dist/api-hub/client/index.js +5 -0
  5. package/dist/api-hub/client/portal-types.js +131 -0
  6. package/dist/api-hub/client/registry-types.js +69 -0
  7. package/dist/api-hub/client/tools.js +98 -0
  8. package/dist/api-hub/client.js +70 -404
  9. package/dist/bugsnag/client/api/CurrentUser.js +19 -13
  10. package/dist/bugsnag/client/api/Error.js +45 -57
  11. package/dist/bugsnag/client/api/Project.js +35 -30
  12. package/dist/bugsnag/client/api/base.js +24 -9
  13. package/dist/bugsnag/client/api/filters.js +9 -9
  14. package/dist/bugsnag/client.js +281 -373
  15. package/dist/common/info.js +1 -1
  16. package/dist/common/server.js +39 -28
  17. package/dist/index.js +18 -4
  18. package/dist/pactflow/client/ai.js +20 -20
  19. package/dist/pactflow/client/base.js +48 -13
  20. package/dist/pactflow/client/prompts.js +10 -12
  21. package/dist/pactflow/client/tools.js +18 -18
  22. package/dist/pactflow/client/utils.js +1 -1
  23. package/dist/pactflow/client.js +23 -15
  24. package/dist/qmetry/client/api/client-api.js +39 -0
  25. package/dist/qmetry/client/handlers.js +11 -0
  26. package/dist/qmetry/client/project.js +27 -0
  27. package/dist/qmetry/client/testcase.js +104 -0
  28. package/dist/qmetry/client/tools.js +222 -0
  29. package/dist/qmetry/client.js +95 -0
  30. package/dist/qmetry/config/constants.js +12 -0
  31. package/dist/qmetry/config/rest-endpoints.js +11 -0
  32. package/dist/qmetry/types/common.js +174 -0
  33. package/dist/qmetry/types/testcase.js +19 -0
  34. package/dist/reflect/client.js +14 -14
  35. package/dist/zephyr/client.js +16 -0
  36. package/dist/zephyr/common/api-client.js +27 -0
  37. package/dist/zephyr/common/auth-service.js +14 -0
  38. package/dist/zephyr/common/types.js +35 -0
  39. package/dist/zephyr/tool/project/get-projects.js +54 -0
  40. package/dist/zephyr/tool/zephyr-tool.js +1 -0
  41. package/package.json +8 -6
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 [BugSnag](https://www.bugsnag.com/), [Reflect](https://reflect.run), [API Hub](https://www.smartbear.com/api-hub), [PactFlow](https://pactflow.io/) and [Pact Broker](https://docs.pact.io/)
21
+ A Model Context Protocol (MCP) server which provides AI assistants with seamless access to SmartBear's suite of testing and monitoring tools, including [BugSnag](https://www.bugsnag.com/), [Reflect](https://reflect.run), [API Hub](https://www.smartbear.com/api-hub), [PactFlow](https://pactflow.io/), [Pact Broker](https://docs.pact.io/), [QMetry](https://www.qmetry.com/), and [Zephyr](https://smartbear.com/test-management/zephyr/).
22
22
 
23
23
  ## What is MCP?
24
24
 
@@ -32,12 +32,14 @@ See individual guides for suggested prompts and supported tools and resources:
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
34
  - [PactFlow](https://developer.smartbear.com/pactflow/default/getting-started) - Contract testing capabilities
35
+ - [QMetry](https://developer.smartbear.com/smartbear-mcp/docs/qmetry-integration) - QMetry Test Management capabilities
36
+ - [Zephyr](https://developer.smartbear.com/smartbear-mcp/docs/zephyr-integration) - Zephyr Test Management capabilities
35
37
 
36
38
 
37
39
  ## Prerequisites
38
40
 
39
41
  - Node.js 20+ and npm
40
- - Access to SmartBear products (BugSnag, Reflect, or API Hub)
42
+ - Access to SmartBear products (BugSnag, Reflect, API Hub, QMetry, or Zephyr)
41
43
  - Valid API tokens for the products you want to integrate
42
44
 
43
45
  ## Installation
@@ -73,7 +75,11 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
73
75
  "PACT_BROKER_BASE_URL": "${input:pact_broker_base_url}",
74
76
  "PACT_BROKER_TOKEN": "${input:pact_broker_token}",
75
77
  "PACT_BROKER_USERNAME": "${input:pact_broker_username}",
76
- "PACT_BROKER_PASSWORD": "${input:pact_broker_password}"
78
+ "PACT_BROKER_PASSWORD": "${input:pact_broker_password}",
79
+ "QMETRY_API_KEY": "${input:qmetry_api_key}",
80
+ "QMETRY_BASE_URL": "${input:qmetry_base_url}",
81
+ "ZEPHYR_API_TOKEN": "${input:zephyr_api_token}",
82
+ "ZEPHYR_BASE_URL": "${input:zephyr_base_url}"
77
83
  }
78
84
  }
79
85
  },
@@ -126,6 +132,30 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
126
132
  "description": "Pact Broker Password",
127
133
  "password": true
128
134
  },
135
+ {
136
+ "id": "qmetry_api_key",
137
+ "type": "promptString",
138
+ "description": "QMetry Open API Key",
139
+ "password": true
140
+ },
141
+ {
142
+ "id": "qmetry_base_url",
143
+ "type": "promptString",
144
+ "description": "By default, connects to https://testmanagement.qmetry.com. Change to a custom QMetry server URL or a region-specific endpoint if needed.",
145
+ "password": false
146
+ },
147
+ {
148
+ "id": "zephyr_api_token",
149
+ "type": "promptString",
150
+ "description": "Zephyr API token - leave blank to disable Zephyr tools",
151
+ "password": true
152
+ },
153
+ {
154
+ "id": "zephyr_base_url",
155
+ "type": "promptString",
156
+ "description": "Zephyr API base URL. By default, connects to https://api.zephyrscale.smartbear.com/v2. Change to region-specific endpoint if needed.",
157
+ "password": false
158
+ }
129
159
  ]
130
160
  }
131
161
  ```
@@ -153,6 +183,10 @@ Add the following configuration to your `claude_desktop_config.json` to launch t
153
183
  "PACT_BROKER_TOKEN": "your_pactflow_token",
154
184
  "PACT_BROKER_USERNAME": "your_pact_broker_username",
155
185
  "PACT_BROKER_PASSWORD": "your_pact_broker_password",
186
+ "QMETRY_API_KEY": "your_qmetry_api_key",
187
+ "QMETRY_BASE_URL": "https://testmanagement.qmetry.com",
188
+ "ZEPHYR_API_TOKEN": "your_zephyr_api_token",
189
+ "ZEPHYR_BASE_URL": "https://api.zephyrscale.smartbear.com/v2"
156
190
  }
157
191
  }
158
192
  }
@@ -0,0 +1,387 @@
1
+ // Regex to extract owner, name, and version from SwaggerHub URLs.
2
+ // Matches /apis/owner/name/version, /domains/owner/name/version, or /templates/owner/name/version
3
+ // Example: /apis/acme/petstore/1.0.0
4
+ // - group 1: type (apis|domains|templates)
5
+ // - group 2: owner
6
+ // - group 3: name
7
+ // - group 4: version
8
+ const SWAGGER_URL_REGEX = /\/(apis|domains|templates)\/([^/]+)\/([^/]+)\/([^/]+)/;
9
+ export class ApiHubAPI {
10
+ config;
11
+ headers;
12
+ constructor(config, userAgent) {
13
+ this.config = config;
14
+ this.headers = config.getHeaders(userAgent);
15
+ }
16
+ /**
17
+ * Core response parsing logic shared between different response handlers.
18
+ * Handles 204 No Content, empty responses, JSON parsing, and text fallbacks.
19
+ * @template T - Expected JSON response data type
20
+ * @template D - Default return type for empty responses
21
+ * @param response - The fetch Response object
22
+ * @param defaultReturn - Default value to return for empty responses
23
+ * @returns Parsed response data or fallback value
24
+ */
25
+ async parseResponse(response, defaultReturn = {}) {
26
+ // Handle 204 No Content responses
27
+ if (response.status === 204) {
28
+ return defaultReturn;
29
+ }
30
+ // Check if response has content-length of 0 (empty body)
31
+ const contentLength = response.headers.get("content-length");
32
+ if (contentLength === "0") {
33
+ return defaultReturn;
34
+ }
35
+ // Check if response is JSON
36
+ const contentType = response.headers.get("content-type");
37
+ if (contentType?.includes("application/json")) {
38
+ try {
39
+ return (await response.json());
40
+ }
41
+ catch (error) {
42
+ console.warn("Failed to parse JSON response (declared JSON):", error);
43
+ return defaultReturn;
44
+ }
45
+ }
46
+ // Fallback: read text and attempt heuristic JSON parse
47
+ const text = await response.text();
48
+ if (!text)
49
+ return defaultReturn;
50
+ const trimmed = text.trim();
51
+ const firstChar = trimmed[0];
52
+ if (firstChar === "{" || firstChar === "[") {
53
+ try {
54
+ return JSON.parse(trimmed);
55
+ }
56
+ catch (error) {
57
+ console.warn("Heuristic JSON parse failed:", error);
58
+ return { message: text };
59
+ }
60
+ }
61
+ return { message: text };
62
+ }
63
+ /**
64
+ * Handles HTTP responses with smart JSON parsing and fallback handling.
65
+ * Includes HTTP error checking before parsing.
66
+ * @template T - Expected response data type
67
+ * @param response - The fetch Response object
68
+ * @param defaultReturn - Default value to return for empty responses
69
+ * @returns Parsed response data or fallback value
70
+ */
71
+ async handleResponse(response, defaultReturn = {}) {
72
+ if (!response.ok) {
73
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
74
+ }
75
+ return this.parseResponse(response, defaultReturn);
76
+ }
77
+ async getPortals() {
78
+ const response = await fetch(`${this.config.portalBasePath}/portals`, {
79
+ method: "GET",
80
+ headers: this.headers,
81
+ });
82
+ const result = await this.handleResponse(response, []);
83
+ return result;
84
+ }
85
+ async createPortal(body) {
86
+ const response = await fetch(`${this.config.portalBasePath}/portals`, {
87
+ method: "POST",
88
+ headers: this.headers,
89
+ body: JSON.stringify(body),
90
+ });
91
+ const result = await this.handleResponse(response);
92
+ if (!("id" in result)) {
93
+ throw new Error("Unexpected empty response creating portal");
94
+ }
95
+ return result;
96
+ }
97
+ async getPortal(portalId) {
98
+ const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
99
+ method: "GET",
100
+ headers: this.headers,
101
+ });
102
+ const result = await this.handleResponse(response);
103
+ if (!("id" in result)) {
104
+ throw new Error("Portal not found or empty response");
105
+ }
106
+ return result;
107
+ }
108
+ async deletePortal(portalId) {
109
+ const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
110
+ method: "DELETE",
111
+ headers: this.headers,
112
+ });
113
+ if (!response.ok) {
114
+ const errorText = await response.text().catch(() => "");
115
+ throw new Error(`API Hub deletePortal failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
116
+ }
117
+ }
118
+ async updatePortal(portalId, body) {
119
+ const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
120
+ method: "PATCH",
121
+ headers: this.headers,
122
+ body: JSON.stringify(body),
123
+ });
124
+ return this.handleResponse(response);
125
+ }
126
+ async getPortalProducts(portalId) {
127
+ const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}/products`, {
128
+ method: "GET",
129
+ headers: this.headers,
130
+ });
131
+ const result = await this.handleResponse(response, []);
132
+ return result;
133
+ }
134
+ async createPortalProduct(portalId, body) {
135
+ const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}/products`, {
136
+ method: "POST",
137
+ headers: this.headers,
138
+ body: JSON.stringify(body),
139
+ });
140
+ const result = await this.handleResponse(response);
141
+ if (!("id" in result)) {
142
+ throw new Error("Unexpected empty response creating product");
143
+ }
144
+ return result;
145
+ }
146
+ async getPortalProduct(productId) {
147
+ const response = await fetch(`${this.config.portalBasePath}/products/${productId}`, {
148
+ method: "GET",
149
+ headers: this.headers,
150
+ });
151
+ const result = await this.handleResponse(response);
152
+ if (!("id" in result)) {
153
+ throw new Error("Product not found or empty response");
154
+ }
155
+ return result;
156
+ }
157
+ async deletePortalProduct(productId) {
158
+ const response = await fetch(`${this.config.portalBasePath}/products/${productId}`, {
159
+ method: "DELETE",
160
+ headers: this.headers,
161
+ });
162
+ return this.handleResponse(response);
163
+ }
164
+ async updatePortalProduct(productId, body) {
165
+ const response = await fetch(`${this.config.portalBasePath}/products/${productId}`, {
166
+ method: "PATCH",
167
+ headers: this.headers,
168
+ body: JSON.stringify(body),
169
+ });
170
+ return this.handleResponse(response, {
171
+ success: true,
172
+ });
173
+ }
174
+ /**
175
+ * Helper method for handling responses when error checking is already done.
176
+ * Delegates to parseResponse for the actual parsing logic.
177
+ * @template T - Expected response data type
178
+ * @template D - Default return type
179
+ * @param response - The fetch Response object
180
+ * @param defaultReturn - Default value to return for empty responses
181
+ * @returns Parsed response data or fallback value
182
+ */
183
+ // Registry API methods for SwaggerHub Design functionality
184
+ /**
185
+ * Search APIs and Domains in SwaggerHub Registry using /specs endpoint
186
+ * @param params Search parameters
187
+ * @returns Array of processed API metadata
188
+ */
189
+ async searchApis(params = {}) {
190
+ const searchParams = new URLSearchParams();
191
+ if (params.query)
192
+ searchParams.append("query", params.query);
193
+ if (params.state)
194
+ searchParams.append("state", params.state);
195
+ if (params.tag)
196
+ searchParams.append("tag", params.tag);
197
+ if (params.offset !== undefined)
198
+ searchParams.append("offset", params.offset.toString());
199
+ if (params.limit !== undefined)
200
+ searchParams.append("limit", params.limit.toString());
201
+ if (params.sort)
202
+ searchParams.append("sort", params.sort);
203
+ if (params.order)
204
+ searchParams.append("order", params.order);
205
+ if (params.owner)
206
+ searchParams.append("owner", params.owner);
207
+ if (params.specType)
208
+ searchParams.append("specType", params.specType);
209
+ const url = `${this.config.registryBasePath}/specs${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
210
+ const response = await fetch(url, {
211
+ method: "GET",
212
+ headers: this.headers,
213
+ });
214
+ if (!response.ok) {
215
+ throw new Error(`SwaggerHub Registry API searchApis failed - status: ${response.status} ${response.statusText}`);
216
+ }
217
+ const apisJsonResponse = (await response.json());
218
+ // Transform APIs.json response to our ApiMetadata format
219
+ return this.transformApisJsonToMetadata(apisJsonResponse.apis);
220
+ }
221
+ /**
222
+ * Transform APIs.json specifications to our ApiMetadata format
223
+ * @param specs Array of API specifications from APIs.json
224
+ * @returns Array of processed API metadata
225
+ */
226
+ transformApisJsonToMetadata(specs) {
227
+ return specs.map((spec) => {
228
+ // Extract useful properties from the properties array
229
+ const properties = spec.properties || [];
230
+ const getProperty = (type) => {
231
+ const property = properties.find((p) => p.type === type);
232
+ return property?.value || property?.url;
233
+ };
234
+ // Extract owner, name, and version from the Swagger URL using the regex constant
235
+ const swaggerUrl = getProperty("Swagger") || "";
236
+ const urlMatch = RegExp(SWAGGER_URL_REGEX).exec(swaggerUrl);
237
+ return {
238
+ owner: urlMatch?.[2] || "",
239
+ name: spec.name || "",
240
+ description: spec.description || "",
241
+ summary: spec.summary || "",
242
+ version: getProperty("X-Version") || urlMatch?.[4] || "",
243
+ specification: getProperty("X-Specification") || "",
244
+ created: getProperty("X-Created"),
245
+ modified: getProperty("X-Modified"),
246
+ published: getProperty("X-Published"),
247
+ private: getProperty("X-Private"),
248
+ oasVersion: getProperty("X-OASVersion"),
249
+ url: swaggerUrl,
250
+ };
251
+ });
252
+ }
253
+ /**
254
+ * Get API definition from SwaggerHub Registry
255
+ * @param params Parameters including owner, api name, version, and options
256
+ * @returns API definition (OpenAPI/Swagger specification)
257
+ */
258
+ async getApiDefinition(params) {
259
+ const searchParams = new URLSearchParams();
260
+ if (params.resolved !== undefined)
261
+ searchParams.append("resolved", params.resolved.toString());
262
+ if (params.flatten !== undefined)
263
+ searchParams.append("flatten", params.flatten.toString());
264
+ const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.api)}/${encodeURIComponent(params.version)}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
265
+ const response = await fetch(url, {
266
+ method: "GET",
267
+ headers: this.headers,
268
+ });
269
+ if (!response.ok) {
270
+ throw new Error(`SwaggerHub Registry API getApiDefinition failed - status: ${response.status} ${response.statusText}`);
271
+ }
272
+ // Return the raw API definition (could be JSON or YAML)
273
+ const contentType = response.headers.get("content-type");
274
+ if (contentType?.includes("application/json")) {
275
+ return response.json();
276
+ }
277
+ else {
278
+ return response.text();
279
+ }
280
+ }
281
+ /**
282
+ * Create or Update API in SwaggerHub Registry
283
+ * @param params Parameters for creating or updating the API including owner, name, version, specification, and definition
284
+ * @returns Created or updated API metadata with URL. HTTP 201 indicates creation, HTTP 200 indicates update
285
+ */
286
+ async createOrUpdateApi(params) {
287
+ // Determine the format of the definition
288
+ let contentType;
289
+ let requestBody;
290
+ // Auto-detect format from the definition content
291
+ const format = this.detectDefinitionFormat(params.definition);
292
+ if (format === "yaml") {
293
+ contentType = "application/yaml";
294
+ requestBody = params.definition; // Send YAML as-is
295
+ }
296
+ else {
297
+ contentType = "application/json";
298
+ // For JSON, parse and stringify to ensure valid JSON
299
+ try {
300
+ const parsedDefinition = JSON.parse(params.definition);
301
+ requestBody = JSON.stringify(parsedDefinition);
302
+ }
303
+ catch (error) {
304
+ throw new Error(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
305
+ }
306
+ }
307
+ // Construct the URL with query parameters
308
+ // Fixed values: visibility=private, automock=false, version=1.0.0
309
+ const searchParams = new URLSearchParams();
310
+ searchParams.append("isPrivate", "true");
311
+ const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.apiName)}?${searchParams.toString()}`;
312
+ // Use POST method with the appropriate content type
313
+ const response = await fetch(url, {
314
+ method: "POST",
315
+ headers: {
316
+ ...this.headers,
317
+ "Content-Type": contentType,
318
+ },
319
+ body: requestBody,
320
+ });
321
+ if (!response.ok) {
322
+ const errorText = await response.text().catch(() => "");
323
+ throw new Error(`SwaggerHub Registry API createOrUpdateApi failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
324
+ }
325
+ // Determine operation type based on HTTP status code
326
+ const operation = response.status === 201 ? "create" : "update";
327
+ // Return formatted response with the required fields
328
+ // Fixed version is always 1.0.0
329
+ return {
330
+ owner: params.owner,
331
+ apiName: params.apiName,
332
+ version: "1.0.0",
333
+ url: `https://app.swaggerhub.com/apis/${params.owner}/${params.apiName}/1.0.0`,
334
+ operation,
335
+ };
336
+ }
337
+ /**
338
+ * Create API from Template in SwaggerHub Registry
339
+ * @param params Parameters for creating API from template including owner, api name, and template
340
+ * @returns Created API metadata with URL. HTTP 201 indicates creation, HTTP 200 indicates update
341
+ */
342
+ async createApiFromTemplate(params) {
343
+ // Construct the URL with query parameters
344
+ // Fixed values: visibility=private, no project, noReconcile=false
345
+ const searchParams = new URLSearchParams();
346
+ searchParams.append("isPrivate", "true");
347
+ searchParams.append("template", params.template);
348
+ const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.apiName)}/.template?${searchParams.toString()}`;
349
+ // Use POST method for template creation
350
+ const response = await fetch(url, {
351
+ method: "POST",
352
+ headers: this.headers,
353
+ });
354
+ if (!response.ok) {
355
+ const errorText = await response.text().catch(() => "");
356
+ throw new Error(`SwaggerHub Registry API createApiFromTemplate failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
357
+ }
358
+ // Determine operation type based on HTTP status code
359
+ const operation = response.status === 201 ? "create" : "update";
360
+ // Return formatted response with the required fields
361
+ return {
362
+ owner: params.owner,
363
+ apiName: params.apiName,
364
+ template: params.template,
365
+ url: `https://app.swaggerhub.com/apis/${params.owner}/${params.apiName}`,
366
+ operation,
367
+ };
368
+ }
369
+ /**
370
+ * Auto-detect the format of an API definition string
371
+ * @param definition The API definition content
372
+ * @returns 'json' or 'yaml'
373
+ */
374
+ detectDefinitionFormat(definition) {
375
+ const trimmed = definition.trim();
376
+ if (!trimmed) {
377
+ throw new Error("Empty definition content provided");
378
+ }
379
+ try {
380
+ JSON.parse(trimmed);
381
+ return "json";
382
+ }
383
+ catch {
384
+ return "yaml";
385
+ }
386
+ }
387
+ }
@@ -0,0 +1,27 @@
1
+ export class ApiHubConfiguration {
2
+ token;
3
+ portalBasePath;
4
+ registryBasePath;
5
+ headers;
6
+ constructor(param) {
7
+ this.token = param.token;
8
+ this.portalBasePath =
9
+ param.portalBasePath || "https://api.portal.swaggerhub.com/v1";
10
+ this.registryBasePath =
11
+ param.registryBasePath || "https://api.swaggerhub.com";
12
+ this.headers = {
13
+ Authorization: `Bearer ${this.token}`,
14
+ "Content-Type": "application/json",
15
+ ...param.headers,
16
+ };
17
+ }
18
+ /**
19
+ * Get headers with User-Agent included
20
+ */
21
+ getHeaders(userAgent) {
22
+ return {
23
+ ...this.headers,
24
+ "User-Agent": userAgent,
25
+ };
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ export { ApiHubAPI } from "./api.js";
2
+ export { ApiHubConfiguration, } from "./configuration.js";
3
+ export * from "./portal-types.js";
4
+ export * from "./registry-types.js";
5
+ export { TOOLS } from "./tools.js";
@@ -0,0 +1,131 @@
1
+ import { z } from "zod";
2
+ // Zod schemas for Portal API validation
3
+ export const PortalArgsSchema = z.object({
4
+ portalId: z
5
+ .string()
6
+ .describe("Portal UUID or subdomain - unique identifier for the portal instance"),
7
+ });
8
+ export const ProductArgsSchema = z.object({
9
+ productId: z
10
+ .string()
11
+ .describe("Product UUID or identifier in the format 'portal-subdomain:product-slug' - unique identifier for the product"),
12
+ });
13
+ export const CreatePortalArgsSchema = z.object({
14
+ name: z
15
+ .string()
16
+ .optional()
17
+ .describe("The display name for the portal - shown to users and in branding (3-40 characters)"),
18
+ subdomain: z
19
+ .string()
20
+ .describe("The portal subdomain - used in the portal URL (e.g., 'myportal' for myportal.example.com). Must be unique, lowercase, 3-20 characters, alphanumeric with hyphens"),
21
+ offline: z
22
+ .boolean()
23
+ .optional()
24
+ .describe("If true, the portal will not be visible to customers - useful for development/staging environments. Defaults to false"),
25
+ routing: z
26
+ .string()
27
+ .optional()
28
+ .describe("Routing strategy for the portal - either 'browser' (client-side routing) or 'proxy' (server-side routing). Defaults to 'browser'"),
29
+ credentialsEnabled: z
30
+ .boolean()
31
+ .optional()
32
+ .describe("Whether authentication credentials are enabled for accessing the portal. When true, users can authenticate to access private content. Defaults to true"),
33
+ swaggerHubOrganizationId: z
34
+ .string()
35
+ .describe("The corresponding SwaggerHub organization UUID - required for portal creation. This links the portal to your SwaggerHub organization"),
36
+ openapiRenderer: z
37
+ .string()
38
+ .optional()
39
+ .describe("OpenAPI renderer type: 'SWAGGER_UI' (Swagger UI), 'ELEMENTS' (Stoplight Elements), or 'TOGGLE' (allows switching between both with Elements as default). Defaults to 'TOGGLE'"),
40
+ pageContentFormat: z
41
+ .string()
42
+ .optional()
43
+ .describe("Format for page content rendering - determines how documentation pages are processed: 'HTML', 'MARKDOWN', or 'BOTH'. Defaults to 'HTML'"),
44
+ });
45
+ export const UpdatePortalArgsSchema = PortalArgsSchema.extend({
46
+ name: z
47
+ .string()
48
+ .optional()
49
+ .describe("Update the portal display name - shown to users and in branding (3-40 characters)"),
50
+ subdomain: z
51
+ .string()
52
+ .optional()
53
+ .describe("Update the portal subdomain - changes the portal URL. Must remain unique across all portals (3-20 characters, lowercase, alphanumeric with hyphens)"),
54
+ customDomain: z
55
+ .boolean()
56
+ .optional()
57
+ .describe("Enable/disable custom domain for the portal - allows using your own domain instead of the default subdomain"),
58
+ gtmKey: z
59
+ .string()
60
+ .optional()
61
+ .describe("Google Tag Manager key for analytics tracking - format: GTM-XXXXXX (max 25 characters)"),
62
+ offline: z
63
+ .boolean()
64
+ .optional()
65
+ .describe("Set portal visibility - true hides portal from customers (useful for maintenance or development)"),
66
+ routing: z
67
+ .string()
68
+ .optional()
69
+ .describe("Update routing strategy - 'browser' for client-side routing or 'proxy' for server-side routing"),
70
+ credentialsEnabled: z
71
+ .boolean()
72
+ .optional()
73
+ .describe("Enable/disable authentication credentials for portal access - controls whether users can authenticate to view private content"),
74
+ openapiRenderer: z
75
+ .string()
76
+ .optional()
77
+ .describe("Change OpenAPI renderer: 'SWAGGER_UI' (Swagger UI), 'ELEMENTS' (Stoplight Elements), or 'TOGGLE' (switch between both)"),
78
+ pageContentFormat: z
79
+ .string()
80
+ .optional()
81
+ .describe("Update page content format for documentation rendering: 'HTML', 'MARKDOWN', or 'BOTH'"),
82
+ });
83
+ export const CreateProductArgsSchema = PortalArgsSchema.extend({
84
+ type: z
85
+ .string()
86
+ .describe("Product creation type - 'new' to create from scratch or 'copy' to duplicate an existing product"),
87
+ name: z
88
+ .string()
89
+ .describe("Product display name - will be shown to users in the portal navigation and product listings (3-40 characters)"),
90
+ slug: z
91
+ .string()
92
+ .describe("URL-friendly identifier for the product - must be unique within the portal, used in URLs (e.g., 'my-api' becomes /my-api). 3-22 characters, lowercase, alphanumeric with hyphens, underscores, or dots"),
93
+ description: z
94
+ .string()
95
+ .optional()
96
+ .describe("Product description - explains what the API/product does, shown in product listings and cards (max 110 characters)"),
97
+ public: z
98
+ .boolean()
99
+ .optional()
100
+ .describe("Whether the product is publicly visible to all portal visitors - false means only authenticated users with appropriate roles can access it"),
101
+ hidden: z
102
+ .boolean()
103
+ .optional()
104
+ .describe("Whether the product is hidden from the portal landing page navigation menus - useful for internal or draft products"),
105
+ role: z
106
+ .boolean()
107
+ .optional()
108
+ .describe("Whether the product has role-based access restrictions - controls if specific user roles are required to access the product"),
109
+ });
110
+ export const UpdateProductArgsSchema = ProductArgsSchema.extend({
111
+ name: z
112
+ .string()
113
+ .optional()
114
+ .describe("Update product display name - changes how it appears to users in navigation and listings (3-40 characters)"),
115
+ slug: z
116
+ .string()
117
+ .optional()
118
+ .describe("Update URL-friendly identifier - must remain unique within the portal, affects product URLs (3-22 characters, lowercase, alphanumeric with hyphens/underscores/dots)"),
119
+ description: z
120
+ .string()
121
+ .optional()
122
+ .describe("Update product description - explains the API/product functionality, shown in listings (max 110 characters)"),
123
+ public: z
124
+ .boolean()
125
+ .optional()
126
+ .describe("Change product visibility - true makes it publicly accessible to all visitors, false restricts to authenticated users with roles"),
127
+ hidden: z
128
+ .boolean()
129
+ .optional()
130
+ .describe("Change navigation visibility - true hides from portal landing page menus while keeping the product accessible via direct links"),
131
+ });