@smartbear/mcp 0.19.2 → 0.21.1
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 +37 -7
- package/assets/icon.png +0 -0
- package/dist/bugsnag/client.js +19 -12
- package/dist/collaborator/client.js +10 -10
- package/dist/common/client-registry.js +2 -2
- package/dist/common/register-clients.js +2 -0
- package/dist/common/server.js +74 -111
- package/dist/common/shutdown.js +165 -0
- package/dist/common/transport-http.js +94 -12
- package/dist/common/transport-stdio.js +16 -2
- package/dist/common/zod-utils.js +62 -7
- package/dist/index.js +2 -0
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/prompts.js +19 -18
- package/dist/pactflow/client/tools.js +8 -13
- package/dist/pactflow/client.js +26 -12
- package/dist/qmetry/client/tools/testsuite-tools.js +2 -2
- package/dist/qmetry/client.js +1 -1
- package/dist/qtm4j/client.js +109 -0
- package/dist/qtm4j/config/constants.js +169 -0
- package/dist/qtm4j/config/field-resolution.types.js +34 -0
- package/dist/qtm4j/http/api-client.js +123 -0
- package/dist/qtm4j/http/auth-service.js +23 -0
- package/dist/qtm4j/resolver/cache/cache.js +52 -0
- package/dist/qtm4j/resolver/resolver-registry.js +70 -0
- package/dist/qtm4j/resolver/resolvers/common-attribute-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/component-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/label-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/resolver.js +6 -0
- package/dist/qtm4j/resolver/resolvers/test-case-uid-resolver.js +28 -0
- package/dist/qtm4j/schema/get-test-case.schema.js +153 -0
- package/dist/qtm4j/schema/get-test-steps.schema.js +74 -0
- package/dist/qtm4j/schema/project.schema.js +43 -0
- package/dist/qtm4j/schema/test-case.schema.js +41 -0
- package/dist/qtm4j/schema/update-test-case.schema.js +45 -0
- package/dist/qtm4j/tool/project/get-projects.js +111 -0
- package/dist/qtm4j/tool/project/set-project-context.js +99 -0
- package/dist/qtm4j/tool/test-case/create-test-case.js +113 -0
- package/dist/qtm4j/tool/test-case/get-test-cases.js +295 -0
- package/dist/qtm4j/tool/test-case/get-test-steps.js +111 -0
- package/dist/qtm4j/tool/test-case/update-test-case.js +158 -0
- package/dist/reflect/client.js +3 -3
- package/dist/reflect/prompt/sap-test.js +5 -5
- package/dist/reflect/tool/recording/add-prompt-step.js +6 -14
- package/dist/reflect/tool/recording/add-segment.js +4 -14
- package/dist/reflect/tool/recording/connect-to-session.js +3 -8
- package/dist/reflect/tool/recording/delete-previous-step.js +3 -8
- package/dist/reflect/tool/recording/get-screenshot.js +4 -14
- package/dist/reflect/tool/suites/cancel-suite-execution.js +4 -14
- package/dist/reflect/tool/suites/execute-suite.js +3 -8
- package/dist/reflect/tool/suites/get-suite-execution-status.js +4 -14
- package/dist/reflect/tool/suites/list-suite-executions.js +3 -8
- package/dist/reflect/tool/suites/list-suites.js +2 -1
- package/dist/reflect/tool/tests/get-test-status.js +3 -8
- package/dist/reflect/tool/tests/list-segments.js +5 -20
- package/dist/reflect/tool/tests/list-tests.js +2 -1
- package/dist/reflect/tool/tests/run-test.js +3 -8
- package/dist/swagger/client/api.js +11 -2
- package/dist/swagger/client/portal-types.js +0 -3
- package/dist/swagger/client/tools.js +0 -1
- package/dist/swagger/client.js +1 -1
- package/dist/zephyr/client.js +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
+
import { TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
3
|
+
import { ResolverKeys, InputField } from "../../config/field-resolution.types.js";
|
|
4
|
+
import { UpdateTestCaseResponse, UpdateTestCaseBody } from "../../schema/update-test-case.schema.js";
|
|
5
|
+
const SIMPLE_FIELD_CONFIG = {
|
|
6
|
+
[InputField.PRIORITY]: ResolverKeys.CommonAttribute.PRIORITY,
|
|
7
|
+
[InputField.STATUS]: ResolverKeys.CommonAttribute.TESTCASE_STATUS
|
|
8
|
+
};
|
|
9
|
+
const ADD_DELETE_FIELD_CONFIG = {
|
|
10
|
+
[InputField.LABELS]: ResolverKeys.SearchableField.LABEL,
|
|
11
|
+
[InputField.COMPONENTS]: ResolverKeys.SearchableField.COMPONENTS
|
|
12
|
+
};
|
|
13
|
+
async function resolveAddDelete(resolver, inputField, resolverKey, field, context, warnings) {
|
|
14
|
+
const result = {};
|
|
15
|
+
for (const op of ["add", "delete"]) {
|
|
16
|
+
const names = field[op];
|
|
17
|
+
if (!names?.length) continue;
|
|
18
|
+
const tempBody = { [inputField]: names };
|
|
19
|
+
await resolver.resolve(
|
|
20
|
+
inputField,
|
|
21
|
+
resolverKey,
|
|
22
|
+
tempBody,
|
|
23
|
+
context,
|
|
24
|
+
warnings
|
|
25
|
+
);
|
|
26
|
+
const val = tempBody[inputField];
|
|
27
|
+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === "number") {
|
|
28
|
+
result[op] = val;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
class UpdateTestCase extends Tool {
|
|
34
|
+
specification = {
|
|
35
|
+
title: TOOL_NAMES.UPDATE_TEST_CASE.TITLE,
|
|
36
|
+
summary: TOOL_NAMES.UPDATE_TEST_CASE.SUMMARY,
|
|
37
|
+
readOnly: false,
|
|
38
|
+
idempotent: true,
|
|
39
|
+
inputSchema: UpdateTestCaseBody,
|
|
40
|
+
outputSchema: UpdateTestCaseResponse,
|
|
41
|
+
purpose: "Update an existing test case in QTM4J using its human-readable key (e.g. 'SCRUM-TC-145'). The key is automatically resolved to the internal UID and latest version — no manual ID lookup needed. Only the fields you provide are changed — omitted fields are left as-is. For priority and status, supply the human-readable name and it is auto-resolved to the internal ID. For labels and components, use the add/delete object to atomically add or remove entries by name. PREREQUISITE: set_project_context must be called before this tool.",
|
|
42
|
+
useCases: [
|
|
43
|
+
"Change the priority of a test case (e.g., escalate to 'High')",
|
|
44
|
+
"Update the status of a test case after review",
|
|
45
|
+
"Add new labels or remove outdated ones without affecting other labels",
|
|
46
|
+
"Add or remove components from a test case",
|
|
47
|
+
"Update summary, description, or precondition text",
|
|
48
|
+
"Reassign a test case to a different team member",
|
|
49
|
+
"Set or update the estimated time for a test case",
|
|
50
|
+
"Batch-update metadata as part of sprint planning"
|
|
51
|
+
],
|
|
52
|
+
examples: [
|
|
53
|
+
{
|
|
54
|
+
description: "Change the priority of a test case",
|
|
55
|
+
parameters: { key: "SCRUM-TC-145", priority: "High" },
|
|
56
|
+
expectedOutput: "Test case updated with new priority"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
description: "Add a label and remove an old one",
|
|
60
|
+
parameters: {
|
|
61
|
+
key: "SCRUM-TC-145",
|
|
62
|
+
labels: { add: ["Release_2"], delete: ["Release_1"] }
|
|
63
|
+
},
|
|
64
|
+
expectedOutput: "Test case updated — Release_2 added, Release_1 removed"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
description: "Update summary, status, and add a component",
|
|
68
|
+
parameters: {
|
|
69
|
+
key: "SCRUM-TC-32",
|
|
70
|
+
summary: "Verify login with MFA enabled",
|
|
71
|
+
status: "In Progress",
|
|
72
|
+
components: { add: ["Auth"] }
|
|
73
|
+
},
|
|
74
|
+
expectedOutput: "Test case summary and status updated, Auth component added"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
description: "Update a specific version",
|
|
78
|
+
parameters: {
|
|
79
|
+
key: "SCRUM-TC-85",
|
|
80
|
+
versionNo: 2,
|
|
81
|
+
assignee: "5b10a2844c20165700ede21f",
|
|
82
|
+
estimatedTime: "01:30:00"
|
|
83
|
+
},
|
|
84
|
+
expectedOutput: "Version 2 of test case updated with new assignee and estimated time"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
hints: [
|
|
88
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
89
|
+
"KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
|
|
90
|
+
"Priority and status values come from set_project_context. Use NLP to map user intent to available names.",
|
|
91
|
+
"If priority or status name is not found, the field is skipped with a warning and other fields are still updated.",
|
|
92
|
+
"Labels and components use add/delete — you can add and delete in a single call. Names are auto-resolved.",
|
|
93
|
+
"To delete ALL current entries of any add/delete field: first call search_test_cases with filter.searchText set to the test case key and include the relevant field in the fields list, extract all current names from the response, then pass them in the delete array of this tool.",
|
|
94
|
+
"Only provide the fields you want to change. Omitted fields remain unchanged on the server.",
|
|
95
|
+
"estimatedTime must be in HH:MM:SS format (e.g., '02:30:00').",
|
|
96
|
+
"versionNo defaults to the latest version. Use search_test_cases to find available versions if needed."
|
|
97
|
+
],
|
|
98
|
+
outputDescription: "Confirmation object with the test case key, versionNo updated, and updated: true. Warnings are included if any field names could not be resolved."
|
|
99
|
+
};
|
|
100
|
+
handle = async (rawArgs) => {
|
|
101
|
+
const args = UpdateTestCaseBody.parse(rawArgs);
|
|
102
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
103
|
+
const context = fieldResolver.requireProjectContext();
|
|
104
|
+
const warnings = [];
|
|
105
|
+
const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.key]);
|
|
106
|
+
const entry = uidMap[args.key];
|
|
107
|
+
if (!entry) {
|
|
108
|
+
throw new ToolError(
|
|
109
|
+
`Test case '${args.key}' not found in project '${context.projectKey}'. Verify the key using the search_test_cases tool.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const versionNo = args.versionNo ?? entry.latestVersion;
|
|
113
|
+
const {
|
|
114
|
+
key: _key,
|
|
115
|
+
versionNo: _vno,
|
|
116
|
+
labels,
|
|
117
|
+
components,
|
|
118
|
+
...scalarArgs
|
|
119
|
+
} = args;
|
|
120
|
+
const body = { ...scalarArgs };
|
|
121
|
+
await Promise.all(
|
|
122
|
+
Object.entries(SIMPLE_FIELD_CONFIG).map(
|
|
123
|
+
([inputField, resolverKey]) => fieldResolver.getResolver(resolverKey).resolve(inputField, resolverKey, body, context, warnings)
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
for (const field of Object.keys(SIMPLE_FIELD_CONFIG)) {
|
|
127
|
+
if (typeof body[field] === "string") delete body[field];
|
|
128
|
+
}
|
|
129
|
+
for (const [inputField, resolverKey] of Object.entries(
|
|
130
|
+
ADD_DELETE_FIELD_CONFIG
|
|
131
|
+
)) {
|
|
132
|
+
const field = args[inputField];
|
|
133
|
+
if (!field) continue;
|
|
134
|
+
const resolvedField = await resolveAddDelete(
|
|
135
|
+
fieldResolver.getResolver(resolverKey),
|
|
136
|
+
inputField,
|
|
137
|
+
resolverKey,
|
|
138
|
+
field,
|
|
139
|
+
context,
|
|
140
|
+
warnings
|
|
141
|
+
);
|
|
142
|
+
if (Object.keys(resolvedField).length > 0)
|
|
143
|
+
body[inputField] = resolvedField;
|
|
144
|
+
}
|
|
145
|
+
await this.client.getApiClient().put(ENDPOINTS.UPDATE_TEST_CASE(entry.uid, versionNo), body);
|
|
146
|
+
return {
|
|
147
|
+
structuredContent: UpdateTestCaseResponse.parse({
|
|
148
|
+
key: args.key,
|
|
149
|
+
versionNo,
|
|
150
|
+
updated: true
|
|
151
|
+
}),
|
|
152
|
+
content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
UpdateTestCase
|
|
158
|
+
};
|
package/dist/reflect/client.js
CHANGED
|
@@ -27,7 +27,7 @@ class ReflectClient {
|
|
|
27
27
|
sessionStates = /* @__PURE__ */ new Map();
|
|
28
28
|
mcpSessionConnections = /* @__PURE__ */ new Map();
|
|
29
29
|
name = "Reflect";
|
|
30
|
-
|
|
30
|
+
capabilityPrefix = "reflect";
|
|
31
31
|
configPrefix = "Reflect";
|
|
32
32
|
config = ConfigurationSchema;
|
|
33
33
|
async configure(_server, config, _cache) {
|
|
@@ -136,10 +136,10 @@ class ReflectClient {
|
|
|
136
136
|
register(tool.specification, tool.handle);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
registerPrompts(register) {
|
|
139
|
+
async registerPrompts(register) {
|
|
140
140
|
const prompts = [new SapTest(this)];
|
|
141
141
|
for (const prompt of prompts) {
|
|
142
|
-
register(prompt.
|
|
142
|
+
register(prompt.specification, prompt.callback);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { Prompt } from "../../common/prompts.js";
|
|
2
3
|
class SapTest extends Prompt {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
title: "Reflect SAP Test",
|
|
4
|
+
specification = {
|
|
5
|
+
title: "SAP Test",
|
|
6
6
|
description: "Guidelines for creating a Reflect test against an SAP S4/HANA or SAP BTP application.",
|
|
7
|
-
argsSchema: {}
|
|
7
|
+
argsSchema: z.object({})
|
|
8
8
|
};
|
|
9
|
-
callback = () => ({
|
|
9
|
+
callback = (_args) => ({
|
|
10
10
|
messages: [
|
|
11
11
|
{
|
|
12
12
|
role: "user",
|
|
@@ -7,20 +7,12 @@ class AddPromptStep extends Tool {
|
|
|
7
7
|
summary: "Add a natural language prompt step to an active Reflect recording session",
|
|
8
8
|
readOnly: false,
|
|
9
9
|
idempotent: false,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "prompt",
|
|
19
|
-
type: z.string(),
|
|
20
|
-
description: 'The natural language prompt describing the test step. The prompt should describe a single action, assertion, or query. The prompt can only contain literal text; it cannot contain template variables, secrets, or other dynamic syntax. If we are in a Web recording, the prompt can perform browser navigation (e.g. "Click on the back button", "Navigate to https://www.example.com") and use the tab and enter keys to navigate (e.g. "Press the tab key", "Press the enter key").',
|
|
21
|
-
required: true
|
|
22
|
-
}
|
|
23
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
sessionId: z.string().describe("The ID of the Reflect recording session"),
|
|
12
|
+
prompt: z.string().describe(
|
|
13
|
+
"The natural language prompt describing the test step. The prompt should describe a single action, assertion, or query. The prompt can only contain literal text; it cannot contain template variables, secrets, or other dynamic syntax. If we are in a Web recording, the prompt can perform browser navigation (e.g. 'Click on the back button', 'Navigate to https://www.example.com') and use the tab and enter keys to navigate (e.g. 'Press the tab key', 'Press the enter key')."
|
|
14
|
+
)
|
|
15
|
+
})
|
|
24
16
|
};
|
|
25
17
|
handle = async (args) => {
|
|
26
18
|
const { sessionId, prompt } = args;
|
|
@@ -7,20 +7,10 @@ class AddSegment extends Tool {
|
|
|
7
7
|
summary: "Insert a reusable test segment into an active Reflect recording session",
|
|
8
8
|
readOnly: false,
|
|
9
9
|
idempotent: false,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
description: "The ID of the Reflect recording session",
|
|
15
|
-
required: true
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "segmentId",
|
|
19
|
-
type: z.number(),
|
|
20
|
-
description: "The ID of the segment to add",
|
|
21
|
-
required: true
|
|
22
|
-
}
|
|
23
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
sessionId: z.string().describe("The ID of the Reflect recording session"),
|
|
12
|
+
segmentId: z.number().describe("The ID of the segment to add")
|
|
13
|
+
})
|
|
24
14
|
};
|
|
25
15
|
handle = async (args) => {
|
|
26
16
|
const { sessionId, segmentId } = args;
|
|
@@ -16,14 +16,9 @@ class ConnectToSession extends Tool {
|
|
|
16
16
|
7. After completing a task, if the task required multiple prompt steps, add a final prompt step that validates the current state of the page based on what you see on the screen. In your validation, do not reference information that can change from run to run.`,
|
|
17
17
|
readOnly: false,
|
|
18
18
|
idempotent: true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type: z.string(),
|
|
23
|
-
description: "The ID of the Reflect recording session to connect to",
|
|
24
|
-
required: true
|
|
25
|
-
}
|
|
26
|
-
]
|
|
19
|
+
inputSchema: z.object({
|
|
20
|
+
sessionId: z.string().describe("The ID of the Reflect recording session to connect to")
|
|
21
|
+
})
|
|
27
22
|
};
|
|
28
23
|
handle = async (args, extra) => {
|
|
29
24
|
const { sessionId } = args;
|
|
@@ -8,14 +8,9 @@ class DeletePreviousStep extends Tool {
|
|
|
8
8
|
readOnly: false,
|
|
9
9
|
idempotent: false,
|
|
10
10
|
destructive: true,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type: z.string(),
|
|
15
|
-
description: "The ID of the Reflect recording session",
|
|
16
|
-
required: true
|
|
17
|
-
}
|
|
18
|
-
]
|
|
11
|
+
inputSchema: z.object({
|
|
12
|
+
sessionId: z.string().describe("The ID of the Reflect recording session")
|
|
13
|
+
})
|
|
19
14
|
};
|
|
20
15
|
handle = async (args) => {
|
|
21
16
|
const { sessionId } = args;
|
|
@@ -7,20 +7,10 @@ class GetScreenshot extends Tool {
|
|
|
7
7
|
summary: "Capture a screenshot from the current state of an active Reflect recording session",
|
|
8
8
|
readOnly: true,
|
|
9
9
|
idempotent: true,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
description: "The ID of the Reflect recording session",
|
|
15
|
-
required: true
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "format",
|
|
19
|
-
type: z.enum(["png", "jpeg"]),
|
|
20
|
-
description: "The image format for the screenshot (png or jpeg)",
|
|
21
|
-
required: false
|
|
22
|
-
}
|
|
23
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
sessionId: z.string().describe("The ID of the Reflect recording session"),
|
|
12
|
+
format: z.enum(["png", "jpeg"]).optional().describe("The image format for the screenshot (png or jpeg)")
|
|
13
|
+
})
|
|
24
14
|
};
|
|
25
15
|
handle = async (args) => {
|
|
26
16
|
const { sessionId, format } = args;
|
|
@@ -5,20 +5,10 @@ class CancelSuiteExecution extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Cancel Suite Execution",
|
|
7
7
|
summary: "Cancel a reflect suite execution",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
description: "ID of the reflect suite to cancel execution for",
|
|
13
|
-
required: true
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: "executionId",
|
|
17
|
-
type: z.string(),
|
|
18
|
-
description: "ID of the reflect suite execution to cancel",
|
|
19
|
-
required: true
|
|
20
|
-
}
|
|
21
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
suiteId: z.string().describe("ID of the reflect suite to cancel execution for"),
|
|
10
|
+
executionId: z.string().describe("ID of the reflect suite execution to cancel")
|
|
11
|
+
})
|
|
22
12
|
};
|
|
23
13
|
handle = async (args) => {
|
|
24
14
|
const { suiteId, executionId } = args;
|
|
@@ -5,14 +5,9 @@ class ExecuteSuite extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Execute Suite",
|
|
7
7
|
summary: "Execute a reflect suite",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type: z.string(),
|
|
12
|
-
description: "ID of the reflect suite to execute",
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
suiteId: z.string().describe("ID of the reflect suite to execute")
|
|
10
|
+
})
|
|
16
11
|
};
|
|
17
12
|
handle = async (args) => {
|
|
18
13
|
const { suiteId } = args;
|
|
@@ -5,20 +5,10 @@ class GetSuiteExecutionStatus extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Get Suite Execution Status",
|
|
7
7
|
summary: "Get the status of a reflect suite execution",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
description: "ID of the reflect suite to get execution status for",
|
|
13
|
-
required: true
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: "executionId",
|
|
17
|
-
type: z.string(),
|
|
18
|
-
description: "ID of the reflect suite execution to get status for",
|
|
19
|
-
required: true
|
|
20
|
-
}
|
|
21
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
suiteId: z.string().describe("ID of the reflect suite to get execution status for"),
|
|
10
|
+
executionId: z.string().describe("ID of the reflect suite execution to get status for")
|
|
11
|
+
})
|
|
22
12
|
};
|
|
23
13
|
handle = async (args) => {
|
|
24
14
|
const { suiteId, executionId } = args;
|
|
@@ -5,14 +5,9 @@ class ListSuiteExecutions extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "List Suite Executions",
|
|
7
7
|
summary: "List all executions for a given suite",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type: z.string(),
|
|
12
|
-
description: "ID of the reflect suite to list executions for",
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
suiteId: z.string().describe("ID of the reflect suite to list executions for")
|
|
10
|
+
})
|
|
16
11
|
};
|
|
17
12
|
handle = async (args) => {
|
|
18
13
|
const { suiteId } = args;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
3
|
import { API_HOSTNAME } from "../../config/constants.js";
|
|
3
4
|
class ListSuites extends Tool {
|
|
4
5
|
specification = {
|
|
5
6
|
title: "List Suites",
|
|
6
7
|
summary: "Retrieve a list of all reflect suites available",
|
|
7
|
-
|
|
8
|
+
inputSchema: z.object({})
|
|
8
9
|
};
|
|
9
10
|
handle = async (_args) => {
|
|
10
11
|
const response = await fetch(`https://${API_HOSTNAME}/v1/suites`, {
|
|
@@ -5,14 +5,9 @@ class GetTestStatus extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Get Test Status",
|
|
7
7
|
summary: "Get the status of a reflect test execution",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type: z.string(),
|
|
12
|
-
description: "ID of the reflect test execution to get status for",
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
executionId: z.string().describe("ID of the reflect test execution to get status for")
|
|
10
|
+
})
|
|
16
11
|
};
|
|
17
12
|
handle = async (args) => {
|
|
18
13
|
const { executionId } = args;
|
|
@@ -7,26 +7,11 @@ class ListSegments extends Tool {
|
|
|
7
7
|
summary: "Retrieve available reusable test segments for the given platform type. Segments are reusable test steps with an optional set of parameters that can used across multiple tests.",
|
|
8
8
|
readOnly: true,
|
|
9
9
|
idempotent: true,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
required: true
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "offset",
|
|
19
|
-
type: z.number(),
|
|
20
|
-
description: "Offset for pagination",
|
|
21
|
-
required: false
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: "limit",
|
|
25
|
-
type: z.number(),
|
|
26
|
-
description: "Maximum number of segments to return",
|
|
27
|
-
required: false
|
|
28
|
-
}
|
|
29
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
platform: z.enum(["api", "native-mobile", "web"]).describe("The platform type to retrieve segments for"),
|
|
12
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
13
|
+
limit: z.number().optional().describe("Maximum number of segments to return")
|
|
14
|
+
})
|
|
30
15
|
};
|
|
31
16
|
handle = async (args) => {
|
|
32
17
|
const {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
3
|
import { API_HOSTNAME } from "../../config/constants.js";
|
|
3
4
|
class ListTests extends Tool {
|
|
4
5
|
specification = {
|
|
5
6
|
title: "List Tests",
|
|
6
7
|
summary: "List all reflect tests",
|
|
7
|
-
|
|
8
|
+
inputSchema: z.object({})
|
|
8
9
|
};
|
|
9
10
|
handle = async (_args) => {
|
|
10
11
|
const response = await fetch(`https://${API_HOSTNAME}/v1/tests`, {
|
|
@@ -5,14 +5,9 @@ class RunTest extends Tool {
|
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Run Test",
|
|
7
7
|
summary: "Run a reflect test",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type: z.string(),
|
|
12
|
-
description: "ID of the reflect test to run",
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
]
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
testId: z.string().describe("ID of the reflect test to run")
|
|
10
|
+
})
|
|
16
11
|
};
|
|
17
12
|
handle = async (args) => {
|
|
18
13
|
const { testId } = args;
|
|
@@ -37,7 +37,7 @@ class SwaggerAPI {
|
|
|
37
37
|
return defaultReturn;
|
|
38
38
|
}
|
|
39
39
|
const contentType = response.headers.get("content-type");
|
|
40
|
-
if (contentType?.includes("application/json")) {
|
|
40
|
+
if (contentType?.includes("application/json") || contentType?.includes("application/problem+json")) {
|
|
41
41
|
try {
|
|
42
42
|
return await response.json();
|
|
43
43
|
} catch (error) {
|
|
@@ -69,7 +69,16 @@ class SwaggerAPI {
|
|
|
69
69
|
*/
|
|
70
70
|
async handleResponse(response, defaultReturn = {}) {
|
|
71
71
|
if (!response.ok) {
|
|
72
|
-
|
|
72
|
+
const errorBody = await this.parseResponse(
|
|
73
|
+
response,
|
|
74
|
+
{}
|
|
75
|
+
);
|
|
76
|
+
const detail = typeof errorBody === "object" && errorBody !== null && String(
|
|
77
|
+
errorBody.detail ?? errorBody.message ?? ""
|
|
78
|
+
) || response.statusText;
|
|
79
|
+
throw new ToolError(
|
|
80
|
+
`HTTP ${response.status}${detail ? `: ${detail}` : ""}`
|
|
81
|
+
);
|
|
73
82
|
}
|
|
74
83
|
return this.parseResponse(
|
|
75
84
|
response,
|
|
@@ -157,9 +157,6 @@ const CreateProductArgsSchema = PortalArgsSchema.extend({
|
|
|
157
157
|
),
|
|
158
158
|
hidden: z.boolean().optional().describe(
|
|
159
159
|
"Whether the product is hidden from the portal landing page navigation menus - useful for internal or draft products"
|
|
160
|
-
),
|
|
161
|
-
role: z.boolean().optional().describe(
|
|
162
|
-
"Whether the product has role-based access restrictions - controls if specific user roles are required to access the product"
|
|
163
160
|
)
|
|
164
161
|
});
|
|
165
162
|
const UpdateProductArgsSchema = ProductArgsSchema.extend({
|
|
@@ -5,7 +5,6 @@ const TOOLS = [
|
|
|
5
5
|
{
|
|
6
6
|
title: "List Portals",
|
|
7
7
|
summary: "Search for available portals within Swagger. Only portals where you have at least a designer role, either at the product level or organization level, are returned.",
|
|
8
|
-
parameters: [],
|
|
9
8
|
handler: "getPortals"
|
|
10
9
|
},
|
|
11
10
|
{
|
package/dist/swagger/client.js
CHANGED
package/dist/zephyr/client.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"smartbear",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"reflect",
|
|
10
10
|
"swagger",
|
|
11
11
|
"pactflow",
|
|
12
|
-
"zephyr"
|
|
12
|
+
"zephyr",
|
|
13
|
+
"qtm4j"
|
|
13
14
|
],
|
|
14
15
|
"homepage": "https://developer.smartbear.com/smartbear-mcp",
|
|
15
16
|
"mcpName": "com.smartbear/smartbear-mcp",
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"mcpServerName": "SmartBear MCP Server"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
|
-
"build": "vite build && shx chmod +x dist/*.js",
|
|
35
|
+
"build": "tsc && vite build && shx chmod +x dist/*.js",
|
|
36
|
+
"build:mcpb": "npm run build && node scripts/pack-mcpb.js",
|
|
35
37
|
"lint": "biome lint .",
|
|
36
38
|
"lint:fix": "biome lint . --fix",
|
|
37
39
|
"format": "biome format . --write",
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
"test:coverage": "vitest --coverage",
|
|
44
46
|
"test:coverage:ci": "vitest --coverage --reporter=verbose",
|
|
45
47
|
"test:run": "vitest run",
|
|
46
|
-
"coverage:check": "vitest --coverage --reporter=verbose
|
|
48
|
+
"coverage:check": "vitest --coverage --reporter=verbose",
|
|
47
49
|
"bump": "node scripts/bump.js"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|