@smartbear/mcp 0.8.0 → 0.10.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 +29 -4
- package/dist/api-hub/client/api.js +239 -10
- package/dist/api-hub/client/configuration.js +5 -0
- package/dist/api-hub/client/index.js +1 -0
- package/dist/api-hub/client/portal-types.js +126 -0
- package/dist/api-hub/client/registry-types.js +8 -0
- package/dist/api-hub/client/tools.js +78 -15
- package/dist/api-hub/client/user-management-types.js +24 -0
- package/dist/api-hub/client.js +34 -0
- package/dist/bugsnag/client/api/CurrentUser.js +12 -49
- package/dist/bugsnag/client/api/Error.js +29 -142
- package/dist/bugsnag/client/api/Project.js +52 -113
- package/dist/bugsnag/client/api/api.js +3743 -0
- package/dist/bugsnag/client/api/base.js +97 -34
- package/dist/bugsnag/client/api/configuration.js +26 -0
- package/dist/bugsnag/client/api/index.js +2 -0
- package/dist/bugsnag/client/filters.js +28 -0
- package/dist/bugsnag/client.js +104 -155
- package/dist/collaborator/client.js +364 -0
- package/dist/common/server.js +73 -23
- package/dist/common/types.js +6 -1
- package/dist/index.js +9 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- package/dist/pactflow/client/tools.js +4 -4
- package/dist/pactflow/client/utils.js +5 -4
- package/dist/pactflow/client.js +10 -9
- package/dist/qmetry/client/api/client-api.js +21 -16
- package/dist/qmetry/client/api/error-handler.js +329 -0
- package/dist/qmetry/client/auto-resolve.js +96 -0
- package/dist/qmetry/client/handlers.js +33 -2
- package/dist/qmetry/client/issues.js +123 -0
- package/dist/qmetry/client/project.js +73 -0
- package/dist/qmetry/client/requirement.js +76 -0
- package/dist/qmetry/client/testcase.js +122 -6
- package/dist/qmetry/client/testsuite.js +272 -0
- package/dist/qmetry/client/tools/index.js +17 -0
- package/dist/qmetry/client/tools/issue-tools.js +545 -0
- package/dist/qmetry/client/tools/project-tools.js +348 -0
- package/dist/qmetry/client/tools/requirement-tools.js +530 -0
- package/dist/qmetry/client/tools/testcase-tools.js +526 -0
- package/dist/qmetry/client/tools/testsuite-tools.js +772 -0
- package/dist/qmetry/client/tools/types.js +1 -0
- package/dist/qmetry/client/utils.js +16 -0
- package/dist/qmetry/client.js +27 -17
- package/dist/qmetry/config/constants.js +28 -0
- package/dist/qmetry/config/rest-endpoints.js +30 -0
- package/dist/qmetry/types/common.js +599 -9
- package/dist/qmetry/types/issues.js +16 -0
- package/dist/qmetry/types/project.js +17 -0
- package/dist/qmetry/types/requirements.js +19 -0
- package/dist/qmetry/types/testcase.js +20 -0
- package/dist/qmetry/types/testsuite.js +44 -0
- package/dist/reflect/client.js +7 -6
- package/dist/zephyr/client.js +7 -1
- package/dist/zephyr/common/api-client.js +8 -0
- package/dist/zephyr/common/auth-service.js +1 -0
- package/dist/zephyr/common/rest-api-schemas.js +5173 -0
- package/dist/zephyr/tool/project/get-project.js +39 -0
- package/dist/zephyr/tool/project/get-projects.js +7 -13
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +72 -0
- package/package.json +1 -1
- package/dist/bugsnag/client/api/filters.js +0 -167
- package/dist/bugsnag/client/configuration.js +0 -10
- package/dist/bugsnag/client/index.js +0 -2
- package/dist/qmetry/client/tools.js +0 -222
- package/dist/zephyr/common/types.js +0 -35
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- Badges -->
|
|
11
11
|
<div>
|
|
12
|
-
<a href="https://github.com/SmartBear/smartbear-mcp/actions/workflows/
|
|
12
|
+
<a href="https://github.com/SmartBear/smartbear-mcp/actions/workflows/test.yml"><img src="https://github.com/SmartBear/smartbear-mcp/actions/workflows/test.yml/badge.svg?branch=main" alt="Test Status"></a>
|
|
13
13
|
<a href="https://smartbear.github.io/smartbear-mcp/"><img src="https://img.shields.io/badge/coverage-dynamic-brightgreen" alt="Coverage"></a>
|
|
14
14
|
<a href="https://www.npmjs.com/package/@smartbear/mcp"><img src="https://img.shields.io/npm/v/@smartbear/mcp" alt="npm version"></a>
|
|
15
15
|
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-Compatible-blue" alt="MCP Compatible"></a>
|
|
@@ -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/), [Pact Broker](https://docs.pact.io/), [QMetry](https://www.qmetry.com/),
|
|
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/), [Zephyr](https://smartbear.com/test-management/zephyr/) and [Collaborator](https://smartbear.com/product/collaborator/).
|
|
22
22
|
|
|
23
23
|
## What is MCP?
|
|
24
24
|
|
|
@@ -34,6 +34,7 @@ See individual guides for suggested prompts and supported tools and resources:
|
|
|
34
34
|
- [PactFlow](https://developer.smartbear.com/pactflow/default/getting-started) - Contract testing capabilities
|
|
35
35
|
- [QMetry](https://developer.smartbear.com/smartbear-mcp/docs/qmetry-integration) - QMetry Test Management capabilities
|
|
36
36
|
- [Zephyr](https://developer.smartbear.com/smartbear-mcp/docs/zephyr-integration) - Zephyr Test Management capabilities
|
|
37
|
+
- [Collaborator](https://developer.smartbear.com/smartbear-mcp/docs/collaborator-integration) - Review and Remote System Configuration management capabilities
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
## Prerequisites
|
|
@@ -79,7 +80,10 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
79
80
|
"QMETRY_API_KEY": "${input:qmetry_api_key}",
|
|
80
81
|
"QMETRY_BASE_URL": "${input:qmetry_base_url}",
|
|
81
82
|
"ZEPHYR_API_TOKEN": "${input:zephyr_api_token}",
|
|
82
|
-
"ZEPHYR_BASE_URL": "${input:zephyr_base_url}"
|
|
83
|
+
"ZEPHYR_BASE_URL": "${input:zephyr_base_url}",
|
|
84
|
+
"COLLAB_BASE_URL": "${input:collab_base_url}",
|
|
85
|
+
"COLLAB_USERNAME": "${input:collab_username}",
|
|
86
|
+
"COLLAB_LOGIN_TICKET": "${input:collab_login_ticket}"
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
},
|
|
@@ -155,6 +159,24 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
155
159
|
"type": "promptString",
|
|
156
160
|
"description": "Zephyr API base URL. By default, connects to https://api.zephyrscale.smartbear.com/v2. Change to region-specific endpoint if needed.",
|
|
157
161
|
"password": false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "collab_base_url",
|
|
165
|
+
"type": "promptString",
|
|
166
|
+
"description": "Collab base url",
|
|
167
|
+
"password": true
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "collab_username",
|
|
171
|
+
"type": "promptString",
|
|
172
|
+
"description": "Collab username",
|
|
173
|
+
"password": true
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "collab_login_ticket",
|
|
177
|
+
"type": "promptString",
|
|
178
|
+
"description": "Collab login ticket",
|
|
179
|
+
"password": true
|
|
158
180
|
}
|
|
159
181
|
]
|
|
160
182
|
}
|
|
@@ -186,7 +208,10 @@ Add the following configuration to your `claude_desktop_config.json` to launch t
|
|
|
186
208
|
"QMETRY_API_KEY": "your_qmetry_api_key",
|
|
187
209
|
"QMETRY_BASE_URL": "https://testmanagement.qmetry.com",
|
|
188
210
|
"ZEPHYR_API_TOKEN": "your_zephyr_api_token",
|
|
189
|
-
"ZEPHYR_BASE_URL": "https://api.zephyrscale.smartbear.com/v2"
|
|
211
|
+
"ZEPHYR_BASE_URL": "https://api.zephyrscale.smartbear.com/v2",
|
|
212
|
+
"COLLAB_BASE_URL": "your collab base url",
|
|
213
|
+
"COLLAB_USERNAME": "your collab user name",
|
|
214
|
+
"COLLAB_LOGIN_TICKET": "your collab login ticket"
|
|
190
215
|
}
|
|
191
216
|
}
|
|
192
217
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolError } from "../../common/types.js";
|
|
1
2
|
// Regex to extract owner, name, and version from SwaggerHub URLs.
|
|
2
3
|
// Matches /apis/owner/name/version, /domains/owner/name/version, or /templates/owner/name/version
|
|
3
4
|
// Example: /apis/acme/petstore/1.0.0
|
|
@@ -70,7 +71,7 @@ export class ApiHubAPI {
|
|
|
70
71
|
*/
|
|
71
72
|
async handleResponse(response, defaultReturn = {}) {
|
|
72
73
|
if (!response.ok) {
|
|
73
|
-
throw new
|
|
74
|
+
throw new ToolError(`HTTP ${response.status}: ${response.statusText}`);
|
|
74
75
|
}
|
|
75
76
|
return this.parseResponse(response, defaultReturn);
|
|
76
77
|
}
|
|
@@ -82,6 +83,34 @@ export class ApiHubAPI {
|
|
|
82
83
|
const result = await this.handleResponse(response, []);
|
|
83
84
|
return result;
|
|
84
85
|
}
|
|
86
|
+
async getOrganizations(params) {
|
|
87
|
+
// Build query string if parameters are provided
|
|
88
|
+
const searchParams = new URLSearchParams();
|
|
89
|
+
if (params?.q)
|
|
90
|
+
searchParams.append("q", params.q);
|
|
91
|
+
if (params?.sortBy)
|
|
92
|
+
searchParams.append("sortBy", params.sortBy);
|
|
93
|
+
if (params?.order)
|
|
94
|
+
searchParams.append("order", params.order);
|
|
95
|
+
if (params?.page !== undefined)
|
|
96
|
+
searchParams.append("page", params.page.toString());
|
|
97
|
+
if (params?.pageSize)
|
|
98
|
+
searchParams.append("pageSize", params.pageSize.toString());
|
|
99
|
+
const queryString = searchParams.toString();
|
|
100
|
+
const url = `${this.config.userManagementBasePath}/orgs${queryString ? `?${queryString}` : ""}`;
|
|
101
|
+
const response = await fetch(url, {
|
|
102
|
+
method: "GET",
|
|
103
|
+
headers: this.headers,
|
|
104
|
+
});
|
|
105
|
+
const defaultResponse = {
|
|
106
|
+
items: [],
|
|
107
|
+
totalCount: 0,
|
|
108
|
+
pageSize: 50,
|
|
109
|
+
page: 0,
|
|
110
|
+
};
|
|
111
|
+
const result = await this.handleResponse(response, defaultResponse);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
85
114
|
async createPortal(body) {
|
|
86
115
|
const response = await fetch(`${this.config.portalBasePath}/portals`, {
|
|
87
116
|
method: "POST",
|
|
@@ -101,7 +130,7 @@ export class ApiHubAPI {
|
|
|
101
130
|
});
|
|
102
131
|
const result = await this.handleResponse(response);
|
|
103
132
|
if (!("id" in result)) {
|
|
104
|
-
throw new
|
|
133
|
+
throw new ToolError("Portal not found or empty response");
|
|
105
134
|
}
|
|
106
135
|
return result;
|
|
107
136
|
}
|
|
@@ -112,7 +141,7 @@ export class ApiHubAPI {
|
|
|
112
141
|
});
|
|
113
142
|
if (!response.ok) {
|
|
114
143
|
const errorText = await response.text().catch(() => "");
|
|
115
|
-
throw new
|
|
144
|
+
throw new ToolError(`API Hub deletePortal failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
116
145
|
}
|
|
117
146
|
}
|
|
118
147
|
async updatePortal(portalId, body) {
|
|
@@ -150,7 +179,7 @@ export class ApiHubAPI {
|
|
|
150
179
|
});
|
|
151
180
|
const result = await this.handleResponse(response);
|
|
152
181
|
if (!("id" in result)) {
|
|
153
|
-
throw new
|
|
182
|
+
throw new ToolError("Product not found or empty response");
|
|
154
183
|
}
|
|
155
184
|
return result;
|
|
156
185
|
}
|
|
@@ -171,6 +200,166 @@ export class ApiHubAPI {
|
|
|
171
200
|
success: true,
|
|
172
201
|
});
|
|
173
202
|
}
|
|
203
|
+
async publishPortalProduct(productId, preview = false) {
|
|
204
|
+
const response = await fetch(`${this.config.portalBasePath}/products/${productId}/published-content?preview=${preview}`, {
|
|
205
|
+
method: "PUT",
|
|
206
|
+
headers: this.headers,
|
|
207
|
+
});
|
|
208
|
+
return this.handleResponse(response, {
|
|
209
|
+
success: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async getPortalProductSections(productId, params) {
|
|
213
|
+
const queryParameters = new URLSearchParams();
|
|
214
|
+
if (params.embed) {
|
|
215
|
+
for (const item of params.embed) {
|
|
216
|
+
queryParameters.append("embed", item);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (params.page !== undefined) {
|
|
220
|
+
queryParameters.append("page", params.page.toString());
|
|
221
|
+
}
|
|
222
|
+
if (params.size !== undefined) {
|
|
223
|
+
queryParameters.append("size", params.size.toString());
|
|
224
|
+
}
|
|
225
|
+
const url = `${this.config.portalBasePath}/products/${productId}/sections${queryParameters.toString() ? `?${queryParameters.toString()}` : ""}`;
|
|
226
|
+
const response = await fetch(url, {
|
|
227
|
+
method: "GET",
|
|
228
|
+
headers: this.headers,
|
|
229
|
+
});
|
|
230
|
+
const result = await this.handleResponse(response, []);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Create a new table of contents item in a portal product section
|
|
235
|
+
* @param sectionId - Section ID where the table of contents item will be created
|
|
236
|
+
* @param body - Table of contents creation parameters
|
|
237
|
+
* @returns Created table of contents item with metadata
|
|
238
|
+
*/
|
|
239
|
+
async createTableOfContents(sectionId, body) {
|
|
240
|
+
const url = `${this.config.portalBasePath}/sections/${sectionId}/table-of-contents`;
|
|
241
|
+
const response = await fetch(url, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: this.headers,
|
|
244
|
+
body: JSON.stringify(body),
|
|
245
|
+
});
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
const errorText = await response.text();
|
|
248
|
+
throw new Error(`API Hub createTableOfContents failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
249
|
+
}
|
|
250
|
+
const result = await response.json();
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get table of contents for a section
|
|
255
|
+
* @param args - Parameters for retrieving table of contents
|
|
256
|
+
* @returns List of table of contents items
|
|
257
|
+
*/
|
|
258
|
+
async getTableOfContents(args) {
|
|
259
|
+
const { sectionId, embed, page, size } = args;
|
|
260
|
+
const searchParams = new URLSearchParams();
|
|
261
|
+
if (embed) {
|
|
262
|
+
for (const item of embed) {
|
|
263
|
+
searchParams.append("embed", item);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (page !== undefined) {
|
|
267
|
+
searchParams.set("page", page.toString());
|
|
268
|
+
}
|
|
269
|
+
if (size !== undefined) {
|
|
270
|
+
searchParams.set("size", size.toString());
|
|
271
|
+
}
|
|
272
|
+
const url = `${this.config.portalBasePath}/sections/${sectionId}/table-of-contents${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
|
|
273
|
+
const response = await fetch(url, {
|
|
274
|
+
method: "GET",
|
|
275
|
+
headers: this.headers,
|
|
276
|
+
});
|
|
277
|
+
if (!response.ok) {
|
|
278
|
+
const errorText = await response.text();
|
|
279
|
+
throw new Error(`API Hub getTableOfContents failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
280
|
+
}
|
|
281
|
+
const result = await response.json();
|
|
282
|
+
// The API returns a paginated response, so we extract the items array
|
|
283
|
+
return result.items;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get document content and metadata
|
|
287
|
+
* @param args - Parameters for retrieving document
|
|
288
|
+
* @returns Document with content and metadata
|
|
289
|
+
*/
|
|
290
|
+
async getDocument(args) {
|
|
291
|
+
const { documentId } = args;
|
|
292
|
+
const url = `${this.config.portalBasePath}/documents/${documentId}`;
|
|
293
|
+
const response = await fetch(url, {
|
|
294
|
+
method: "GET",
|
|
295
|
+
headers: this.headers,
|
|
296
|
+
});
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
const errorText = await response.text();
|
|
299
|
+
throw new Error(`API Hub getDocument failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
300
|
+
}
|
|
301
|
+
const result = await response.json();
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Update document content
|
|
306
|
+
* @param args - Parameters for updating document
|
|
307
|
+
* @returns Success response
|
|
308
|
+
*/
|
|
309
|
+
async updateDocument(args) {
|
|
310
|
+
const { documentId, ...body } = args;
|
|
311
|
+
const url = `${this.config.portalBasePath}/documents/${documentId}`;
|
|
312
|
+
const response = await fetch(url, {
|
|
313
|
+
method: "PATCH",
|
|
314
|
+
headers: this.headers,
|
|
315
|
+
body: JSON.stringify(body),
|
|
316
|
+
});
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
const errorText = await response.text();
|
|
319
|
+
throw new Error(`API Hub updateDocument failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
320
|
+
}
|
|
321
|
+
return { success: true };
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Delete document
|
|
325
|
+
* @param args - Parameters for deleting document
|
|
326
|
+
* @returns Success response
|
|
327
|
+
*/
|
|
328
|
+
async deleteDocument(args) {
|
|
329
|
+
const { documentId } = args;
|
|
330
|
+
const url = `${this.config.portalBasePath}/documents/${documentId}`;
|
|
331
|
+
const response = await fetch(url, {
|
|
332
|
+
method: "DELETE",
|
|
333
|
+
headers: this.headers,
|
|
334
|
+
});
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
const errorText = await response.text();
|
|
337
|
+
throw new Error(`API Hub deleteDocument failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
338
|
+
}
|
|
339
|
+
return { success: true };
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Delete table of contents entry
|
|
343
|
+
* @param args - Parameters for deleting table of contents entry
|
|
344
|
+
* @returns Success response
|
|
345
|
+
*/
|
|
346
|
+
async deleteTableOfContents(args) {
|
|
347
|
+
const { tableOfContentsId, recursive } = args;
|
|
348
|
+
const searchParams = new URLSearchParams();
|
|
349
|
+
if (recursive !== undefined) {
|
|
350
|
+
searchParams.set("recursive", recursive.toString());
|
|
351
|
+
}
|
|
352
|
+
const url = `${this.config.portalBasePath}/table-of-contents/${tableOfContentsId}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
|
|
353
|
+
const response = await fetch(url, {
|
|
354
|
+
method: "DELETE",
|
|
355
|
+
headers: this.headers,
|
|
356
|
+
});
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
const errorText = await response.text();
|
|
359
|
+
throw new Error(`API Hub deleteTableOfContents failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
|
|
360
|
+
}
|
|
361
|
+
return { success: true };
|
|
362
|
+
}
|
|
174
363
|
/**
|
|
175
364
|
* Helper method for handling responses when error checking is already done.
|
|
176
365
|
* Delegates to parseResponse for the actual parsing logic.
|
|
@@ -212,7 +401,7 @@ export class ApiHubAPI {
|
|
|
212
401
|
headers: this.headers,
|
|
213
402
|
});
|
|
214
403
|
if (!response.ok) {
|
|
215
|
-
throw new
|
|
404
|
+
throw new ToolError(`SwaggerHub Registry API searchApis failed - status: ${response.status} ${response.statusText}`);
|
|
216
405
|
}
|
|
217
406
|
const apisJsonResponse = (await response.json());
|
|
218
407
|
// Transform APIs.json response to our ApiMetadata format
|
|
@@ -267,7 +456,7 @@ export class ApiHubAPI {
|
|
|
267
456
|
headers: this.headers,
|
|
268
457
|
});
|
|
269
458
|
if (!response.ok) {
|
|
270
|
-
throw new
|
|
459
|
+
throw new ToolError(`SwaggerHub Registry API getApiDefinition failed - status: ${response.status} ${response.statusText}`);
|
|
271
460
|
}
|
|
272
461
|
// Return the raw API definition (could be JSON or YAML)
|
|
273
462
|
const contentType = response.headers.get("content-type");
|
|
@@ -301,7 +490,7 @@ export class ApiHubAPI {
|
|
|
301
490
|
requestBody = JSON.stringify(parsedDefinition);
|
|
302
491
|
}
|
|
303
492
|
catch (error) {
|
|
304
|
-
throw new
|
|
493
|
+
throw new ToolError(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
305
494
|
}
|
|
306
495
|
}
|
|
307
496
|
// Construct the URL with query parameters
|
|
@@ -320,7 +509,7 @@ export class ApiHubAPI {
|
|
|
320
509
|
});
|
|
321
510
|
if (!response.ok) {
|
|
322
511
|
const errorText = await response.text().catch(() => "");
|
|
323
|
-
throw new
|
|
512
|
+
throw new ToolError(`SwaggerHub Registry API createOrUpdateApi failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
324
513
|
}
|
|
325
514
|
// Determine operation type based on HTTP status code
|
|
326
515
|
const operation = response.status === 201 ? "create" : "update";
|
|
@@ -353,7 +542,7 @@ export class ApiHubAPI {
|
|
|
353
542
|
});
|
|
354
543
|
if (!response.ok) {
|
|
355
544
|
const errorText = await response.text().catch(() => "");
|
|
356
|
-
throw new
|
|
545
|
+
throw new ToolError(`SwaggerHub Registry API createApiFromTemplate failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
357
546
|
}
|
|
358
547
|
// Determine operation type based on HTTP status code
|
|
359
548
|
const operation = response.status === 201 ? "create" : "update";
|
|
@@ -374,7 +563,7 @@ export class ApiHubAPI {
|
|
|
374
563
|
detectDefinitionFormat(definition) {
|
|
375
564
|
const trimmed = definition.trim();
|
|
376
565
|
if (!trimmed) {
|
|
377
|
-
throw new
|
|
566
|
+
throw new ToolError("Empty definition content provided");
|
|
378
567
|
}
|
|
379
568
|
try {
|
|
380
569
|
JSON.parse(trimmed);
|
|
@@ -384,4 +573,44 @@ export class ApiHubAPI {
|
|
|
384
573
|
return "yaml";
|
|
385
574
|
}
|
|
386
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Run a standardization scan against an API definition
|
|
578
|
+
* @param params Parameters including organization name and API definition
|
|
579
|
+
* @returns Standardization result with validation errors
|
|
580
|
+
*/
|
|
581
|
+
async scanStandardization(params) {
|
|
582
|
+
// Auto-detect format from the definition content
|
|
583
|
+
const format = this.detectDefinitionFormat(params.definition);
|
|
584
|
+
let contentType;
|
|
585
|
+
let requestBody;
|
|
586
|
+
if (format === "yaml") {
|
|
587
|
+
contentType = "application/yaml";
|
|
588
|
+
requestBody = params.definition; // Send YAML as-is
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
contentType = "application/json";
|
|
592
|
+
// For JSON, parse and stringify to ensure valid JSON
|
|
593
|
+
try {
|
|
594
|
+
const parsedDefinition = JSON.parse(params.definition);
|
|
595
|
+
requestBody = JSON.stringify(parsedDefinition);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
throw new Error(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const url = `${this.config.registryBasePath}/standardization/${encodeURIComponent(params.orgName)}/scan`;
|
|
602
|
+
const response = await fetch(url, {
|
|
603
|
+
method: "POST",
|
|
604
|
+
headers: {
|
|
605
|
+
...this.headers,
|
|
606
|
+
"Content-Type": contentType,
|
|
607
|
+
},
|
|
608
|
+
body: requestBody,
|
|
609
|
+
});
|
|
610
|
+
if (!response.ok) {
|
|
611
|
+
const errorText = await response.text().catch(() => "");
|
|
612
|
+
throw new Error(`SwaggerHub Registry API scanStandardization failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
|
|
613
|
+
}
|
|
614
|
+
return await this.handleResponse(response);
|
|
615
|
+
}
|
|
387
616
|
}
|
|
@@ -2,6 +2,7 @@ export class ApiHubConfiguration {
|
|
|
2
2
|
token;
|
|
3
3
|
portalBasePath;
|
|
4
4
|
registryBasePath;
|
|
5
|
+
userManagementBasePath;
|
|
5
6
|
headers;
|
|
6
7
|
constructor(param) {
|
|
7
8
|
this.token = param.token;
|
|
@@ -9,6 +10,10 @@ export class ApiHubConfiguration {
|
|
|
9
10
|
param.portalBasePath || "https://api.portal.swaggerhub.com/v1";
|
|
10
11
|
this.registryBasePath =
|
|
11
12
|
param.registryBasePath || "https://api.swaggerhub.com";
|
|
13
|
+
this.userManagementBasePath =
|
|
14
|
+
param.userManagementBasePath ||
|
|
15
|
+
"https://api.swaggerhub.com/user-management/v1";
|
|
16
|
+
// Use Bearer token format consistently across all APIs
|
|
12
17
|
this.headers = {
|
|
13
18
|
Authorization: `Bearer ${this.token}`,
|
|
14
19
|
"Content-Type": "application/json",
|
|
@@ -10,6 +10,93 @@ export const ProductArgsSchema = z.object({
|
|
|
10
10
|
.string()
|
|
11
11
|
.describe("Product UUID or identifier in the format 'portal-subdomain:product-slug' - unique identifier for the product"),
|
|
12
12
|
});
|
|
13
|
+
export const GetProductSectionsArgsSchema = z.object({
|
|
14
|
+
productId: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("Product UUID or identifier in the format 'portal-subdomain:product-slug' - unique identifier for the product"),
|
|
17
|
+
embed: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("List of related entities to embed in the response - e.g., ['tableOfContents', 'tableOfContents.swaggerhubApi'] to include table of contents and SwaggerHub API details"),
|
|
21
|
+
page: z
|
|
22
|
+
.number()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Page number for paginated results - specifies which page of results to retrieve (default is 1)"),
|
|
25
|
+
size: z
|
|
26
|
+
.number()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Number of items per page for pagination - controls how many results are returned per page (default is 20)"),
|
|
29
|
+
});
|
|
30
|
+
export const GetTableOfContentsArgsSchema = z.object({
|
|
31
|
+
sectionId: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe("Section ID - unique identifier for the section within the product"),
|
|
34
|
+
embed: z
|
|
35
|
+
.array(z.string())
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("List of related entities to embed in the response - e.g., ['swaggerhubApi'] to include SwaggerHub API details"),
|
|
38
|
+
page: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Page number for paginated results - specifies which page of results to retrieve (default is 1)"),
|
|
42
|
+
size: z
|
|
43
|
+
.number()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Number of items per page for pagination - controls how many results are returned per page (default is 20)"),
|
|
46
|
+
});
|
|
47
|
+
export const CreateTableOfContentsArgsSchema = z.object({
|
|
48
|
+
sectionId: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe("Section ID - unique identifier for the section within the product"),
|
|
51
|
+
type: z
|
|
52
|
+
.enum(["new", "copy"])
|
|
53
|
+
.describe("Type of table of contents creation - 'new' to create from scratch or 'copy' to duplicate an existing one"),
|
|
54
|
+
title: z
|
|
55
|
+
.string()
|
|
56
|
+
.describe("Title of the table of contents item - will be displayed in navigation (3-40 characters)"),
|
|
57
|
+
slug: z
|
|
58
|
+
.string()
|
|
59
|
+
.describe("URL-friendly identifier for the table of contents item - must be unique within the section (3-22 characters, lowercase, alphanumeric with hyphens/underscores/dots)"),
|
|
60
|
+
order: z
|
|
61
|
+
.number()
|
|
62
|
+
.describe("Order position of the table of contents item within its parent section or item"),
|
|
63
|
+
parentId: z
|
|
64
|
+
.string()
|
|
65
|
+
.nullable()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Parent table of contents item ID - null for top-level items, or ID of parent item for nested structure"),
|
|
68
|
+
content: z
|
|
69
|
+
.object({
|
|
70
|
+
type: z
|
|
71
|
+
.enum(["apiUrl", "html", "markdown"])
|
|
72
|
+
.describe("Content type - 'apiUrl' for API references, 'html' for HTML content, or 'markdown' for Markdown content"),
|
|
73
|
+
url: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("URL for API reference content (required when type is 'apiUrl')"),
|
|
77
|
+
apiSpec: z
|
|
78
|
+
.string()
|
|
79
|
+
.nullable()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("API specification format for API URL content"),
|
|
82
|
+
documentId: z
|
|
83
|
+
.string()
|
|
84
|
+
.nullable()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Document ID for HTML or Markdown content"),
|
|
87
|
+
})
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Content configuration for the table of contents item")
|
|
90
|
+
.refine((content) => {
|
|
91
|
+
if (content?.type === "apiUrl") {
|
|
92
|
+
return content.url?.endsWith("/swagger.json");
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}, {
|
|
96
|
+
message: "URL must end with '/swagger.json' when content type is 'apiUrl'",
|
|
97
|
+
path: ["url"],
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
13
100
|
export const CreatePortalArgsSchema = z.object({
|
|
14
101
|
name: z
|
|
15
102
|
.string()
|
|
@@ -129,3 +216,42 @@ export const UpdateProductArgsSchema = ProductArgsSchema.extend({
|
|
|
129
216
|
.optional()
|
|
130
217
|
.describe("Change navigation visibility - true hides from portal landing page menus while keeping the product accessible via direct links"),
|
|
131
218
|
});
|
|
219
|
+
export const PublishProductArgsSchema = ProductArgsSchema.extend({
|
|
220
|
+
preview: z
|
|
221
|
+
.boolean()
|
|
222
|
+
.optional()
|
|
223
|
+
.default(false)
|
|
224
|
+
.describe("Whether to publish as preview (true) or live (false). Preview allows testing before going live. Defaults to false (live publication)"),
|
|
225
|
+
});
|
|
226
|
+
// Document management schemas
|
|
227
|
+
export const GetDocumentArgsSchema = z.object({
|
|
228
|
+
documentId: z
|
|
229
|
+
.string()
|
|
230
|
+
.describe("Document UUID - unique identifier for the document"),
|
|
231
|
+
});
|
|
232
|
+
export const UpdateDocumentArgsSchema = z.object({
|
|
233
|
+
documentId: z
|
|
234
|
+
.string()
|
|
235
|
+
.describe("Document UUID - unique identifier for the document"),
|
|
236
|
+
content: z
|
|
237
|
+
.string()
|
|
238
|
+
.describe("The document content to update (HTML or Markdown based on document type)"),
|
|
239
|
+
type: z
|
|
240
|
+
.enum(["html", "markdown"])
|
|
241
|
+
.optional()
|
|
242
|
+
.describe("Content type - 'html' for HTML content or 'markdown' for Markdown content"),
|
|
243
|
+
});
|
|
244
|
+
export const DeleteDocumentArgsSchema = z.object({
|
|
245
|
+
documentId: z
|
|
246
|
+
.string()
|
|
247
|
+
.describe("Document UUID - unique identifier for the document to delete"),
|
|
248
|
+
});
|
|
249
|
+
export const DeleteTableOfContentsArgsSchema = z.object({
|
|
250
|
+
tableOfContentsId: z
|
|
251
|
+
.string()
|
|
252
|
+
.describe("The table of contents UUID, or identifier in the format 'portal-subdomain:product-slug:section-slug:table-of-contents-slug'"),
|
|
253
|
+
recursive: z
|
|
254
|
+
.boolean()
|
|
255
|
+
.optional()
|
|
256
|
+
.describe("Flag to include all the nested tables of contents (default: false)"),
|
|
257
|
+
});
|
|
@@ -67,3 +67,11 @@ export const CreateApiFromTemplateParamsSchema = z.object({
|
|
|
67
67
|
.string()
|
|
68
68
|
.describe("Template name to use for creating the API. Format: owner/template-name/version (e.g., 'swagger-hub/petstore-template/1.0.0'). API is created with fixed values: private visibility, no project assignment, and reconciliation enabled."),
|
|
69
69
|
});
|
|
70
|
+
export const ScanStandardizationParamsSchema = z.object({
|
|
71
|
+
orgName: z
|
|
72
|
+
.string()
|
|
73
|
+
.describe("The organization name to use for standardization rules"),
|
|
74
|
+
definition: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe("API definition content (OpenAPI/AsyncAPI specification in JSON or YAML format) to scan for standardization errors"),
|
|
77
|
+
});
|