@smartbear/mcp 0.8.0 → 0.9.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/dist/api-hub/client/api.js +51 -10
- package/dist/api-hub/client/registry-types.js +8 -0
- package/dist/api-hub/client/tools.js +7 -1
- package/dist/api-hub/client.js +3 -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 +100 -151
- package/dist/common/server.js +25 -3
- package/dist/common/types.js +6 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- 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 +74 -0
- package/dist/qmetry/client/handlers.js +19 -2
- package/dist/qmetry/client/issues.js +26 -0
- package/dist/qmetry/client/project.js +56 -0
- package/dist/qmetry/client/requirement.js +76 -0
- package/dist/qmetry/client/testcase.js +46 -8
- package/dist/qmetry/client/testsuite.js +117 -0
- package/dist/qmetry/client/tools.js +1455 -4
- package/dist/qmetry/client/utils.js +16 -0
- package/dist/qmetry/client.js +19 -16
- package/dist/qmetry/config/constants.js +14 -0
- package/dist/qmetry/config/rest-endpoints.js +20 -0
- package/dist/qmetry/types/common.js +313 -8
- package/dist/qmetry/types/issues.js +6 -0
- package/dist/qmetry/types/project.js +10 -0
- package/dist/qmetry/types/requirements.js +19 -0
- package/dist/qmetry/types/testcase.js +14 -0
- package/dist/qmetry/types/testsuite.js +26 -0
- package/dist/reflect/client.js +7 -6
- package/dist/zephyr/common/auth-service.js +1 -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
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { ToolError } from "../../../common/types.js";
|
|
1
2
|
// Utility to pick only allowed fields from an object
|
|
2
3
|
export function pickFields(obj, keys) {
|
|
3
4
|
const result = {};
|
|
5
|
+
if (!obj)
|
|
6
|
+
return result;
|
|
4
7
|
for (const key of keys) {
|
|
5
8
|
if (key in obj) {
|
|
6
9
|
result[key] = obj[key];
|
|
@@ -25,7 +28,7 @@ export function getNextUrlPathFromHeader(headers, basePath) {
|
|
|
25
28
|
return match.replace(basePath, "");
|
|
26
29
|
}
|
|
27
30
|
// Utility to extract total count from headers
|
|
28
|
-
|
|
31
|
+
function getTotalCountFromHeader(headers) {
|
|
29
32
|
if (!headers)
|
|
30
33
|
return null;
|
|
31
34
|
const totalCount = headers.get("X-Total-Count");
|
|
@@ -34,12 +37,47 @@ export function getTotalCountFromHeader(headers) {
|
|
|
34
37
|
const parsed = parseInt(totalCount, 10);
|
|
35
38
|
return Number.isNaN(parsed) ? null : parsed;
|
|
36
39
|
}
|
|
40
|
+
// Utility to recursively convert object keys from snake_case to camelCase
|
|
41
|
+
function convertKeysToCamelCase(obj) {
|
|
42
|
+
if (obj === null || obj === undefined) {
|
|
43
|
+
return obj;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(obj)) {
|
|
46
|
+
return obj.map(convertKeysToCamelCase);
|
|
47
|
+
}
|
|
48
|
+
if (typeof obj === "object" && obj.constructor === Object) {
|
|
49
|
+
const converted = {};
|
|
50
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
51
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
52
|
+
converted[camelKey] = convertKeysToCamelCase(value);
|
|
53
|
+
}
|
|
54
|
+
return converted;
|
|
55
|
+
}
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
37
58
|
// Ensure URL is absolute
|
|
38
59
|
// The MCP tools exposed use only the path for pagination
|
|
39
60
|
// For making requests, we need to ensure the URL is absolute
|
|
40
61
|
export function ensureFullUrl(url, basePath) {
|
|
41
62
|
return url.startsWith("http") ? url : `${basePath}${url}`;
|
|
42
63
|
}
|
|
64
|
+
// Merge nextUrl query parameters with options query parameters (usually filters)
|
|
65
|
+
export function getQueryParams(nextUrl, options) {
|
|
66
|
+
const nextOptions = { query: {} };
|
|
67
|
+
if (nextUrl) {
|
|
68
|
+
nextOptions.query = {};
|
|
69
|
+
if (!nextUrl.includes("?")) {
|
|
70
|
+
throw new Error("nextUrl must contains query parameters");
|
|
71
|
+
}
|
|
72
|
+
new URLSearchParams(nextUrl.split("?")[1]).forEach((value, key) => {
|
|
73
|
+
nextOptions.query[key] = value;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (options) {
|
|
77
|
+
nextOptions.query = { ...nextOptions.query, ...options.query };
|
|
78
|
+
}
|
|
79
|
+
return nextOptions;
|
|
80
|
+
}
|
|
43
81
|
export class BaseAPI {
|
|
44
82
|
configuration;
|
|
45
83
|
filterFields;
|
|
@@ -47,53 +85,78 @@ export class BaseAPI {
|
|
|
47
85
|
this.configuration = configuration;
|
|
48
86
|
this.filterFields = filterFields || [];
|
|
49
87
|
}
|
|
50
|
-
async
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
async requestObject(url, options = {}, fields) {
|
|
89
|
+
if (!this.configuration.basePath) {
|
|
90
|
+
throw new Error("Base path is not configured for API requests");
|
|
91
|
+
}
|
|
92
|
+
if (this.configuration.headers) {
|
|
93
|
+
options.headers = {
|
|
94
|
+
...this.configuration.headers,
|
|
95
|
+
...options.headers,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const response = await fetch(ensureFullUrl(url, this.configuration.basePath), {
|
|
99
|
+
...options,
|
|
100
|
+
headers: {
|
|
101
|
+
...options.headers,
|
|
102
|
+
...this.configuration.headers,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const errorText = await response.text();
|
|
107
|
+
throw new Error(`Request failed with status ${response.status}: ${errorText}`);
|
|
108
|
+
}
|
|
109
|
+
const apiResponse = {
|
|
110
|
+
status: response.status,
|
|
111
|
+
headers: response.headers,
|
|
112
|
+
body: convertKeysToCamelCase(await response.json()),
|
|
60
113
|
};
|
|
114
|
+
if (fields) {
|
|
115
|
+
apiResponse.body = pickFields(apiResponse.body, fields);
|
|
116
|
+
}
|
|
117
|
+
if (this.filterFields) {
|
|
118
|
+
this.sanitizeResponse(apiResponse.body);
|
|
119
|
+
}
|
|
120
|
+
return apiResponse;
|
|
121
|
+
}
|
|
122
|
+
async requestArray(url, options = {}, fetchAll = true, fields) {
|
|
61
123
|
let results = [];
|
|
62
|
-
let nextUrl =
|
|
124
|
+
let nextUrl = url;
|
|
63
125
|
let apiResponse;
|
|
64
126
|
do {
|
|
65
|
-
if (!this.configuration.basePath) {
|
|
66
|
-
throw new Error("Base path is not configured for API requests");
|
|
67
|
-
}
|
|
68
127
|
nextUrl = ensureFullUrl(nextUrl, this.configuration.basePath);
|
|
69
|
-
const response = await fetch(nextUrl,
|
|
128
|
+
const response = await fetch(nextUrl, {
|
|
129
|
+
...options,
|
|
130
|
+
headers: {
|
|
131
|
+
...options.headers,
|
|
132
|
+
...this.configuration.headers,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
70
135
|
if (!response.ok) {
|
|
71
136
|
const errorText = await response.text();
|
|
72
|
-
throw new
|
|
137
|
+
throw new ToolError(`Request failed with status ${response.status}: ${errorText}`);
|
|
73
138
|
}
|
|
139
|
+
const data = convertKeysToCamelCase(await response.json());
|
|
140
|
+
nextUrl = getNextUrlPathFromHeader(response.headers, this.configuration.basePath);
|
|
141
|
+
if (!Array.isArray(data)) {
|
|
142
|
+
throw new Error("Expected response to be an array");
|
|
143
|
+
}
|
|
144
|
+
results = results.concat(data);
|
|
74
145
|
apiResponse = {
|
|
75
146
|
status: response.status,
|
|
76
147
|
headers: response.headers,
|
|
148
|
+
nextUrl: nextUrl,
|
|
149
|
+
totalCount: getTotalCountFromHeader(response.headers),
|
|
150
|
+
body: results,
|
|
77
151
|
};
|
|
78
|
-
const data = await response.json();
|
|
79
|
-
nextUrl = getNextUrlPathFromHeader(response.headers, this.configuration.basePath);
|
|
80
|
-
if (Array.isArray(data)) {
|
|
81
|
-
results = results.concat(data);
|
|
82
|
-
apiResponse.nextUrl = nextUrl;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
apiResponse.body = data;
|
|
86
|
-
}
|
|
87
152
|
} while (fetchAll && nextUrl);
|
|
88
|
-
if (
|
|
89
|
-
apiResponse.body =
|
|
90
|
-
apiResponse.totalCount = getTotalCountFromHeader(apiResponse.headers);
|
|
91
|
-
}
|
|
92
|
-
if (Array.isArray(apiResponse.body)) {
|
|
93
|
-
apiResponse.body.forEach(this.sanitizeResponse.bind(this));
|
|
153
|
+
if (fields) {
|
|
154
|
+
apiResponse.body = pickFieldsFromArray(apiResponse.body, fields);
|
|
94
155
|
}
|
|
95
|
-
|
|
96
|
-
|
|
156
|
+
if (this.filterFields) {
|
|
157
|
+
apiResponse.body.forEach((item) => {
|
|
158
|
+
this.sanitizeResponse(item);
|
|
159
|
+
});
|
|
97
160
|
}
|
|
98
161
|
return apiResponse;
|
|
99
162
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class Configuration {
|
|
2
|
+
/**
|
|
3
|
+
* parameter for apiKey security
|
|
4
|
+
* @param name security name
|
|
5
|
+
* @memberof Configuration
|
|
6
|
+
*/
|
|
7
|
+
apiKey;
|
|
8
|
+
/**
|
|
9
|
+
* override base path
|
|
10
|
+
*
|
|
11
|
+
* @type {string}
|
|
12
|
+
* @memberof Configuration
|
|
13
|
+
*/
|
|
14
|
+
basePath;
|
|
15
|
+
/**
|
|
16
|
+
* Additional headers for API requests
|
|
17
|
+
* @type {Record<string, string>}
|
|
18
|
+
* @memberof Configuration
|
|
19
|
+
*/
|
|
20
|
+
headers;
|
|
21
|
+
constructor(param) {
|
|
22
|
+
this.apiKey = param.apiKey;
|
|
23
|
+
this.basePath = param.basePath;
|
|
24
|
+
this.headers = param.headers;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters utility for BugSnag API
|
|
3
|
+
*
|
|
4
|
+
* This file provides utility functions for creating filter URL parameters
|
|
5
|
+
* based on the BugSnag filtering specification described in the Filtering.md document.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
export const FilterValueSchema = z.object({
|
|
9
|
+
type: z.enum(["eq", "ne", "empty"]),
|
|
10
|
+
value: z.union([z.string(), z.boolean(), z.number()]),
|
|
11
|
+
});
|
|
12
|
+
export const FilterObjectSchema = z.record(z.array(FilterValueSchema));
|
|
13
|
+
/**
|
|
14
|
+
* Converts a FilterObject to URL search parameters
|
|
15
|
+
*
|
|
16
|
+
* @param filters The filter object to convert
|
|
17
|
+
* @returns URLSearchParams object with the encoded filters
|
|
18
|
+
*/
|
|
19
|
+
export function toUrlSearchParams(filters) {
|
|
20
|
+
const params = new URLSearchParams();
|
|
21
|
+
Object.entries(filters).forEach(([field, filterValues]) => {
|
|
22
|
+
filterValues.forEach((filterValue) => {
|
|
23
|
+
params.append(`filters[${field}][][type]`, filterValue.type);
|
|
24
|
+
params.append(`filters[${field}][][value]`, filterValue.value.toString());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
return params;
|
|
28
|
+
}
|