@smartbear/mcp 0.5.0 → 0.7.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 (36) hide show
  1. package/README.md +22 -111
  2. package/dist/api-hub/client/api.js +253 -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 +55 -0
  7. package/dist/api-hub/client/tools.js +86 -0
  8. package/dist/api-hub/client.js +64 -404
  9. package/dist/bugsnag/client/api/CurrentUser.js +18 -13
  10. package/dist/bugsnag/client/api/Error.js +35 -35
  11. package/dist/bugsnag/client/api/Project.js +137 -9
  12. package/dist/bugsnag/client/api/base.js +27 -13
  13. package/dist/bugsnag/client/api/filters.js +9 -9
  14. package/dist/bugsnag/client.js +584 -145
  15. package/dist/common/info.js +1 -1
  16. package/dist/common/server.js +44 -27
  17. package/dist/index.js +13 -6
  18. package/dist/pactflow/client/ai.js +33 -2
  19. package/dist/pactflow/client/base.js +51 -3
  20. package/dist/pactflow/client/prompt-utils.js +89 -0
  21. package/dist/pactflow/client/prompts.js +131 -0
  22. package/dist/pactflow/client/tools.js +39 -7
  23. package/dist/pactflow/client/utils.js +1 -1
  24. package/dist/pactflow/client.js +147 -9
  25. package/dist/qmetry/client/api/client-api.js +39 -0
  26. package/dist/qmetry/client/handlers.js +11 -0
  27. package/dist/qmetry/client/project.js +27 -0
  28. package/dist/qmetry/client/testcase.js +104 -0
  29. package/dist/qmetry/client/tools.js +222 -0
  30. package/dist/qmetry/client.js +95 -0
  31. package/dist/qmetry/config/constants.js +12 -0
  32. package/dist/qmetry/config/rest-endpoints.js +11 -0
  33. package/dist/qmetry/types/common.js +174 -0
  34. package/dist/qmetry/types/testcase.js +19 -0
  35. package/dist/reflect/client.js +14 -14
  36. package/package.json +9 -6
@@ -0,0 +1,95 @@
1
+ import { QMETRY_HANDLER_MAP } from "./client/handlers.js";
2
+ import { getProjectInfo } from "./client/project.js";
3
+ import { TOOLS } from "./client/tools.js";
4
+ import { QMETRY_DEFAULTS, QMetryToolsHandlers } from "./config/constants.js";
5
+ export class QmetryClient {
6
+ name = "QMetry";
7
+ prefix = "qmetry";
8
+ token;
9
+ projectApiKey;
10
+ endpoint;
11
+ constructor(token, endpoint) {
12
+ this.token = token;
13
+ this.projectApiKey = QMETRY_DEFAULTS.PROJECT_KEY;
14
+ this.endpoint = endpoint || QMETRY_DEFAULTS.BASE_URL;
15
+ }
16
+ getToken() {
17
+ return this.token;
18
+ }
19
+ getBaseUrl() {
20
+ return this.endpoint;
21
+ }
22
+ registerTools(register, _getInput) {
23
+ const resolveContext = (args) => ({
24
+ baseUrl: args.baseUrl ?? this.endpoint,
25
+ projectKey: args.projectKey ?? this.projectApiKey,
26
+ });
27
+ const handleAsync = async (fn) => {
28
+ try {
29
+ return await fn();
30
+ }
31
+ catch (err) {
32
+ return {
33
+ content: [
34
+ {
35
+ success: false,
36
+ type: "text",
37
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ };
43
+ for (const tool of TOOLS) {
44
+ const handlerFn = QMETRY_HANDLER_MAP[tool.handler];
45
+ if (!handlerFn) {
46
+ console.error(`⚠️ No handler mapped for ${tool.title}`);
47
+ continue;
48
+ }
49
+ register(tool, (args) => handleAsync(async () => {
50
+ const a = args;
51
+ const { baseUrl, projectKey } = resolveContext(a);
52
+ // handling for FETCH_TEST_CASES to auto-resolve viewId and folderPath
53
+ if (tool.handler === QMetryToolsHandlers.FETCH_TEST_CASES) {
54
+ let viewId = a.viewId;
55
+ let folderPath = a.folderPath;
56
+ if (!viewId || folderPath === undefined) {
57
+ let projectInfo;
58
+ try {
59
+ projectInfo = (await getProjectInfo(this.token, baseUrl, projectKey));
60
+ }
61
+ catch (err) {
62
+ throw new Error(`Failed to auto-resolve viewId/folderPath for project ${projectKey}. ` +
63
+ `Please provide them manually or check project access. ` +
64
+ `Error: ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+ if (!viewId && projectInfo?.latestViews?.TC?.viewId) {
67
+ viewId = projectInfo.latestViews.TC.viewId;
68
+ }
69
+ if (folderPath === undefined) {
70
+ folderPath = "";
71
+ }
72
+ }
73
+ a.viewId = viewId;
74
+ a.folderPath = folderPath;
75
+ }
76
+ const result = await handlerFn(this.token, baseUrl, projectKey, a);
77
+ // Use custom formatter if available, otherwise return JSON
78
+ const formatted = tool.formatResponse
79
+ ? tool.formatResponse(result)
80
+ : (result ?? {});
81
+ return {
82
+ content: [
83
+ {
84
+ success: true,
85
+ type: "text",
86
+ text: typeof formatted === "string"
87
+ ? formatted
88
+ : JSON.stringify(formatted, null, 2),
89
+ },
90
+ ],
91
+ };
92
+ }));
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,12 @@
1
+ export const QMETRY_DEFAULTS = {
2
+ BASE_URL: "https://testmanagement.qmetry.com",
3
+ PROJECT_KEY: "default",
4
+ };
5
+ export const QMetryToolsHandlers = {
6
+ SET_PROJECT_INFO: "setProjectInfo",
7
+ FETCH_PROJECT_INFO: "getProjectInfo",
8
+ FETCH_TEST_CASES: "getTestCases",
9
+ FETCH_TEST_CASE_DETAILS: "getTestCaseDetails",
10
+ FETCH_TEST_CASE_VERSION_DETAILS: "getTestCaseVersionDetails",
11
+ FETCH_TEST_CASE_STEPS: "getTestCaseSteps",
12
+ };
@@ -0,0 +1,11 @@
1
+ export const QMETRY_PATHS = {
2
+ PROJECT: {
3
+ GET_INFO: "/rest/admin/project/getinfo",
4
+ },
5
+ TESTCASE: {
6
+ GET_TC_LIST: "/rest/testcases/list/viewColumns",
7
+ GET_TC_DETAILS: "/rest/testcases/getVersionDetail",
8
+ GET_TC_DETAILS_BY_VERSION: "/rest/testcases/list",
9
+ GET_TC_STEPS: "/rest/testcases/steps/list",
10
+ },
11
+ };
@@ -0,0 +1,174 @@
1
+ import { z } from "zod";
2
+ import { QMETRY_DEFAULTS } from "../config/constants.js";
3
+ export const DEFAULT_PAGINATION = {
4
+ start: 0,
5
+ page: 1,
6
+ limit: 10,
7
+ };
8
+ export const DEFAULT_FILTER = {
9
+ filter: "[]",
10
+ };
11
+ export const DEFAULT_FOLDER_OPTIONS = {
12
+ scope: "project",
13
+ showRootOnly: false,
14
+ getSubEntities: true,
15
+ hideEmptyFolders: false,
16
+ folderSortColumn: "name",
17
+ folderSortOrder: "ASC",
18
+ restoreDefaultColumns: false,
19
+ folderID: null,
20
+ };
21
+ // Reusable Zod schema components
22
+ export const CommonFields = {
23
+ projectKey: z
24
+ .string()
25
+ .describe("Project key - unique identifier for the project")
26
+ .default(QMETRY_DEFAULTS.PROJECT_KEY),
27
+ projectKeyOptional: z
28
+ .string()
29
+ .optional()
30
+ .describe("Project key - unique identifier for the project")
31
+ .default(QMETRY_DEFAULTS.PROJECT_KEY),
32
+ baseUrl: z
33
+ .string()
34
+ .url()
35
+ .optional()
36
+ .describe("The base URL for the QMetry instance (must be a valid URL)")
37
+ .default(QMETRY_DEFAULTS.BASE_URL),
38
+ start: z
39
+ .number()
40
+ .optional()
41
+ .describe("Start index for pagination - defaults to 0")
42
+ .default(0),
43
+ page: z
44
+ .number()
45
+ .optional()
46
+ .describe("Page number to return (starts from 1)")
47
+ .default(1),
48
+ limit: z
49
+ .number()
50
+ .optional()
51
+ .describe("Number of records (default 10).")
52
+ .default(10),
53
+ tcID: z
54
+ .number()
55
+ .describe("Test Case numeric ID (required for fetching specific test case details). " +
56
+ "This is the internal numeric identifier, not the entity key like 'MAC-TC-1684'. " +
57
+ "You can get this ID from test case search results or by using filters."),
58
+ id: z
59
+ .number()
60
+ .describe("Test Case numeric ID (required for fetching steps or version details). " +
61
+ "This is the internal numeric identifier, not the entity key like 'MAC-TC-1684'. " +
62
+ "You can get this ID from test case search results."),
63
+ version: z
64
+ .number()
65
+ .describe("Test Case version number (required for fetching specific test case version details). " +
66
+ "This is the internal numeric identifier for the version."),
67
+ versionOptional: z
68
+ .number()
69
+ .optional()
70
+ .describe("Test Case version number (optional, defaults to 1). " +
71
+ "This is the internal numeric identifier for the version."),
72
+ viewId: z
73
+ .number()
74
+ .describe("ViewId for test cases - SYSTEM AUTOMATICALLY RESOLVES THIS. " +
75
+ "Leave empty unless you have a specific viewId. " +
76
+ "System will fetch project info using the projectKey and extract latestViews.TC.viewId automatically. " +
77
+ "Manual viewId only needed if you want to override the automatic resolution."),
78
+ folderPath: z
79
+ .string()
80
+ .optional()
81
+ .describe("Folder path for test cases - SYSTEM AUTOMATICALLY SETS TO ROOT. " +
82
+ 'Leave empty unless you want specific folder. System will automatically use "" (root directory). ' +
83
+ 'Only specify if user wants specific folder like "Automation/Regression".')
84
+ .default(""),
85
+ folderID: z
86
+ .number()
87
+ .optional()
88
+ .describe("Folder ID for test cases - unique identifier for the folder containing test cases"),
89
+ scope: z
90
+ .string()
91
+ .optional()
92
+ .describe("Scope of the test cases (default 'project')")
93
+ .default("project"),
94
+ filter: z
95
+ .string()
96
+ .optional()
97
+ .describe("Filter criteria as JSON string (default '[]')")
98
+ .default("[]"),
99
+ udfFilter: z
100
+ .string()
101
+ .optional()
102
+ .describe("User-defined field filter as JSON string (default '[]')")
103
+ .default("[]"),
104
+ showRootOnly: z
105
+ .boolean()
106
+ .optional()
107
+ .describe("Whether to show only root folders."),
108
+ getSubEntities: z
109
+ .boolean()
110
+ .optional()
111
+ .describe("Whether to include sub-entities."),
112
+ hideEmptyFolders: z
113
+ .boolean()
114
+ .optional()
115
+ .describe("Whether to hide empty folders."),
116
+ folderSortColumn: z
117
+ .string()
118
+ .optional()
119
+ .describe("Folder sort column (default 'name')"),
120
+ restoreDefaultColumns: z
121
+ .boolean()
122
+ .optional()
123
+ .describe("Whether to restore default columns (default 'false')"),
124
+ folderSortOrder: z
125
+ .string()
126
+ .optional()
127
+ .describe("Folder sort order (ASC or DESC)"),
128
+ };
129
+ export const ProjectArgsSchema = z.object({
130
+ projectKey: CommonFields.projectKey,
131
+ });
132
+ export const TestCaseListArgsSchema = z.object({
133
+ projectKey: CommonFields.projectKeyOptional,
134
+ baseUrl: CommonFields.baseUrl,
135
+ viewId: CommonFields.viewId,
136
+ folderPath: CommonFields.folderPath,
137
+ folderID: CommonFields.folderID,
138
+ start: CommonFields.start,
139
+ page: CommonFields.page,
140
+ limit: CommonFields.limit,
141
+ scope: CommonFields.scope,
142
+ showRootOnly: CommonFields.showRootOnly,
143
+ getSubEntities: CommonFields.getSubEntities,
144
+ hideEmptyFolders: CommonFields.hideEmptyFolders,
145
+ folderSortColumn: CommonFields.folderSortColumn,
146
+ restoreDefaultColumns: CommonFields.restoreDefaultColumns,
147
+ folderSortOrder: CommonFields.folderSortOrder,
148
+ filter: CommonFields.filter,
149
+ udfFilter: CommonFields.udfFilter,
150
+ });
151
+ export const TestCaseDetailsArgsSchema = z.object({
152
+ projectKey: CommonFields.projectKeyOptional,
153
+ baseUrl: CommonFields.baseUrl,
154
+ tcID: CommonFields.tcID,
155
+ start: CommonFields.start,
156
+ page: CommonFields.page,
157
+ limit: CommonFields.limit,
158
+ });
159
+ export const TestCaseVersionDetailsArgsSchema = z.object({
160
+ projectKey: CommonFields.projectKeyOptional,
161
+ baseUrl: CommonFields.baseUrl,
162
+ id: CommonFields.id,
163
+ version: CommonFields.version,
164
+ scope: CommonFields.scope,
165
+ });
166
+ export const TestCaseStepsArgsSchema = z.object({
167
+ projectKey: CommonFields.projectKeyOptional,
168
+ baseUrl: CommonFields.baseUrl,
169
+ id: CommonFields.id,
170
+ version: CommonFields.versionOptional,
171
+ start: CommonFields.start,
172
+ page: CommonFields.page,
173
+ limit: CommonFields.limit,
174
+ });
@@ -0,0 +1,19 @@
1
+ import { DEFAULT_FILTER, DEFAULT_FOLDER_OPTIONS, DEFAULT_PAGINATION, } from "./common.js";
2
+ export const DEFAULT_FETCH_TESTCASES_PAYLOAD = {
3
+ ...DEFAULT_PAGINATION,
4
+ ...DEFAULT_FILTER,
5
+ ...DEFAULT_FOLDER_OPTIONS,
6
+ udfFilter: "[]",
7
+ };
8
+ export const DEFAULT_FETCH_TESTCASE_DETAILS_PAYLOAD = {
9
+ ...DEFAULT_PAGINATION,
10
+ ...DEFAULT_FILTER,
11
+ };
12
+ export const DEFAULT_FETCH_TESTCASE_VERSION_DETAILS_PAYLOAD = {
13
+ ...DEFAULT_FILTER,
14
+ scope: DEFAULT_FOLDER_OPTIONS.scope,
15
+ };
16
+ export const DEFAULT_FETCH_TESTCASE_STEPS_PAYLOAD = {
17
+ ...DEFAULT_PAGINATION,
18
+ version: 1,
19
+ };
@@ -1,5 +1,5 @@
1
- import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
2
1
  import { z } from "zod";
2
+ import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
3
3
  // ReflectClient class implementing the Client interface
4
4
  export class ReflectClient {
5
5
  headers;
@@ -61,7 +61,7 @@ export class ReflectClient {
61
61
  });
62
62
  return response.json();
63
63
  }
64
- async getReflectTestStatus(testId, executionId) {
64
+ async getReflectTestStatus(_testId, executionId) {
65
65
  const response = await fetch(`https://api.reflect.run/v1/executions/${executionId}`, {
66
66
  method: "GET",
67
67
  headers: this.headers,
@@ -87,7 +87,7 @@ export class ReflectClient {
87
87
  name: "suiteId",
88
88
  type: z.string(),
89
89
  description: "ID of the reflect suite to list executions for",
90
- required: true
90
+ required: true,
91
91
  },
92
92
  ],
93
93
  }, async (args, _extra) => {
@@ -106,13 +106,13 @@ export class ReflectClient {
106
106
  name: "suiteId",
107
107
  type: z.string(),
108
108
  description: "ID of the reflect suite to get execution status for",
109
- required: true
109
+ required: true,
110
110
  },
111
111
  {
112
112
  name: "executionId",
113
113
  type: z.string(),
114
114
  description: "ID of the reflect suite execution to get status for",
115
- required: true
115
+ required: true,
116
116
  },
117
117
  ],
118
118
  }, async (args, _extra) => {
@@ -131,9 +131,9 @@ export class ReflectClient {
131
131
  name: "suiteId",
132
132
  type: z.string(),
133
133
  description: "ID of the reflect suite to list executions for",
134
- required: true
135
- }
136
- ]
134
+ required: true,
135
+ },
136
+ ],
137
137
  }, async (args, _extra) => {
138
138
  if (!args.suiteId)
139
139
  throw new Error("suiteId argument is required");
@@ -150,13 +150,13 @@ export class ReflectClient {
150
150
  name: "suiteId",
151
151
  type: z.string(),
152
152
  description: "ID of the reflect suite to cancel execution for",
153
- required: true
153
+ required: true,
154
154
  },
155
155
  {
156
156
  name: "executionId",
157
157
  type: z.string(),
158
158
  description: "ID of the reflect suite execution to cancel",
159
- required: true
159
+ required: true,
160
160
  },
161
161
  ],
162
162
  }, async (args, _extra) => {
@@ -185,8 +185,8 @@ export class ReflectClient {
185
185
  name: "testId",
186
186
  type: z.string(),
187
187
  description: "ID of the reflect test to run",
188
- required: true
189
- }
188
+ required: true,
189
+ },
190
190
  ],
191
191
  }, async (args, _extra) => {
192
192
  if (!args.testId)
@@ -204,13 +204,13 @@ export class ReflectClient {
204
204
  name: "testId",
205
205
  type: z.string(),
206
206
  description: "ID of the reflect test to run",
207
- required: true
207
+ required: true,
208
208
  },
209
209
  {
210
210
  name: "executionId",
211
211
  type: z.string(),
212
212
  description: "ID of the reflect test execution to get status for",
213
- required: true
213
+ required: true,
214
214
  },
215
215
  ],
216
216
  }, async (args, _extra) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartbear/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "MCP server for interacting SmartBear Products",
5
5
  "keywords": [
6
6
  "smartbear",
@@ -11,6 +11,7 @@
11
11
  "pactflow"
12
12
  ],
13
13
  "homepage": "https://developer.smartbear.com/smartbear-mcp",
14
+ "mcpName": "com.smartbear/smartbear-mcp",
14
15
  "repository": {
15
16
  "type": "git",
16
17
  "url": "git@github.com:SmartBear/smartbear-mcp.git"
@@ -30,7 +31,10 @@
30
31
  },
31
32
  "scripts": {
32
33
  "build": "tsc && shx chmod +x dist/*.js",
33
- "lint": "eslint . --ext .ts",
34
+ "lint": "biome lint .",
35
+ "lint:fix": "biome lint . --fix",
36
+ "format": "biome format . --write",
37
+ "format:check": "biome format .",
34
38
  "prepare": "npm run build",
35
39
  "watch": "tsc --watch",
36
40
  "test": "vitest",
@@ -45,18 +49,17 @@
45
49
  "@modelcontextprotocol/sdk": "^1.15.0",
46
50
  "node-cache": "^5.1.2",
47
51
  "swagger-client": "^3.35.6",
48
- "zod": "^3"
52
+ "zod": "^3",
53
+ "zod-to-json-schema": "^3.24.6"
49
54
  },
50
55
  "devDependencies": {
51
- "@eslint/js": "^9.29.0",
56
+ "@biomejs/biome": "^2.2.4",
52
57
  "@types/js-yaml": "^4.0.9",
53
58
  "@types/node": "^22",
54
59
  "@vitest/coverage-v8": "^3.2.4",
55
- "eslint": "^9.29.0",
56
60
  "globals": "^16.2.0",
57
61
  "shx": "^0.3.4",
58
62
  "typescript": "^5.6.2",
59
- "typescript-eslint": "^8.34.1",
60
63
  "vitest": "^3.2.4",
61
64
  "vitest-fetch-mock": "^0.4.5"
62
65
  }