@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.
Files changed (44) hide show
  1. package/dist/api-hub/client/api.js +51 -10
  2. package/dist/api-hub/client/registry-types.js +8 -0
  3. package/dist/api-hub/client/tools.js +7 -1
  4. package/dist/api-hub/client.js +3 -0
  5. package/dist/bugsnag/client/api/CurrentUser.js +12 -49
  6. package/dist/bugsnag/client/api/Error.js +29 -142
  7. package/dist/bugsnag/client/api/Project.js +52 -113
  8. package/dist/bugsnag/client/api/api.js +3743 -0
  9. package/dist/bugsnag/client/api/base.js +97 -34
  10. package/dist/bugsnag/client/api/configuration.js +26 -0
  11. package/dist/bugsnag/client/api/index.js +2 -0
  12. package/dist/bugsnag/client/filters.js +28 -0
  13. package/dist/bugsnag/client.js +100 -151
  14. package/dist/common/server.js +25 -3
  15. package/dist/common/types.js +6 -1
  16. package/dist/pactflow/client/prompt-utils.js +2 -1
  17. package/dist/pactflow/client/utils.js +5 -4
  18. package/dist/pactflow/client.js +10 -9
  19. package/dist/qmetry/client/api/client-api.js +21 -16
  20. package/dist/qmetry/client/api/error-handler.js +329 -0
  21. package/dist/qmetry/client/auto-resolve.js +74 -0
  22. package/dist/qmetry/client/handlers.js +19 -2
  23. package/dist/qmetry/client/issues.js +26 -0
  24. package/dist/qmetry/client/project.js +56 -0
  25. package/dist/qmetry/client/requirement.js +76 -0
  26. package/dist/qmetry/client/testcase.js +46 -8
  27. package/dist/qmetry/client/testsuite.js +117 -0
  28. package/dist/qmetry/client/tools.js +1455 -4
  29. package/dist/qmetry/client/utils.js +16 -0
  30. package/dist/qmetry/client.js +19 -16
  31. package/dist/qmetry/config/constants.js +14 -0
  32. package/dist/qmetry/config/rest-endpoints.js +20 -0
  33. package/dist/qmetry/types/common.js +313 -8
  34. package/dist/qmetry/types/issues.js +6 -0
  35. package/dist/qmetry/types/project.js +10 -0
  36. package/dist/qmetry/types/requirements.js +19 -0
  37. package/dist/qmetry/types/testcase.js +14 -0
  38. package/dist/qmetry/types/testsuite.js +26 -0
  39. package/dist/reflect/client.js +7 -6
  40. package/dist/zephyr/common/auth-service.js +1 -0
  41. package/package.json +1 -1
  42. package/dist/bugsnag/client/api/filters.js +0 -167
  43. package/dist/bugsnag/client/configuration.js +0 -10
  44. 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
- export function getTotalCountFromHeader(headers) {
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 request(options, fetchAll = true) {
51
- const headers = {
52
- ...this.configuration.headers,
53
- ...options.headers,
54
- };
55
- headers.Authorization = `token ${this.configuration.authToken}`;
56
- const fetchOptions = {
57
- method: options.method,
58
- headers,
59
- body: options.body ? JSON.stringify(options.body) : undefined,
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 = options.url;
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, fetchOptions);
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 Error(`Request failed with status ${response.status}: ${errorText}`);
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 (results.length > 0) {
89
- apiResponse.body = results;
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
- else {
96
- this.sanitizeResponse(apiResponse.body ?? {});
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
+ }
@@ -1,2 +1,4 @@
1
1
  export { CurrentUserAPI } from "./CurrentUser.js";
2
+ export { Configuration } from "./configuration.js";
2
3
  export { ErrorAPI } from "./Error.js";
4
+ export { ProjectAPI } from "./Project.js";
@@ -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
+ }