@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.
Files changed (63) hide show
  1. package/README.md +37 -7
  2. package/assets/icon.png +0 -0
  3. package/dist/bugsnag/client.js +19 -12
  4. package/dist/collaborator/client.js +10 -10
  5. package/dist/common/client-registry.js +2 -2
  6. package/dist/common/register-clients.js +2 -0
  7. package/dist/common/server.js +74 -111
  8. package/dist/common/shutdown.js +165 -0
  9. package/dist/common/transport-http.js +94 -12
  10. package/dist/common/transport-stdio.js +16 -2
  11. package/dist/common/zod-utils.js +62 -7
  12. package/dist/index.js +2 -0
  13. package/dist/package.json.js +1 -1
  14. package/dist/pactflow/client/prompts.js +19 -18
  15. package/dist/pactflow/client/tools.js +8 -13
  16. package/dist/pactflow/client.js +26 -12
  17. package/dist/qmetry/client/tools/testsuite-tools.js +2 -2
  18. package/dist/qmetry/client.js +1 -1
  19. package/dist/qtm4j/client.js +109 -0
  20. package/dist/qtm4j/config/constants.js +169 -0
  21. package/dist/qtm4j/config/field-resolution.types.js +34 -0
  22. package/dist/qtm4j/http/api-client.js +123 -0
  23. package/dist/qtm4j/http/auth-service.js +23 -0
  24. package/dist/qtm4j/resolver/cache/cache.js +52 -0
  25. package/dist/qtm4j/resolver/resolver-registry.js +70 -0
  26. package/dist/qtm4j/resolver/resolvers/common-attribute-resolver.js +56 -0
  27. package/dist/qtm4j/resolver/resolvers/component-resolver.js +56 -0
  28. package/dist/qtm4j/resolver/resolvers/label-resolver.js +56 -0
  29. package/dist/qtm4j/resolver/resolvers/resolver.js +6 -0
  30. package/dist/qtm4j/resolver/resolvers/test-case-uid-resolver.js +28 -0
  31. package/dist/qtm4j/schema/get-test-case.schema.js +153 -0
  32. package/dist/qtm4j/schema/get-test-steps.schema.js +74 -0
  33. package/dist/qtm4j/schema/project.schema.js +43 -0
  34. package/dist/qtm4j/schema/test-case.schema.js +41 -0
  35. package/dist/qtm4j/schema/update-test-case.schema.js +45 -0
  36. package/dist/qtm4j/tool/project/get-projects.js +111 -0
  37. package/dist/qtm4j/tool/project/set-project-context.js +99 -0
  38. package/dist/qtm4j/tool/test-case/create-test-case.js +113 -0
  39. package/dist/qtm4j/tool/test-case/get-test-cases.js +295 -0
  40. package/dist/qtm4j/tool/test-case/get-test-steps.js +111 -0
  41. package/dist/qtm4j/tool/test-case/update-test-case.js +158 -0
  42. package/dist/reflect/client.js +3 -3
  43. package/dist/reflect/prompt/sap-test.js +5 -5
  44. package/dist/reflect/tool/recording/add-prompt-step.js +6 -14
  45. package/dist/reflect/tool/recording/add-segment.js +4 -14
  46. package/dist/reflect/tool/recording/connect-to-session.js +3 -8
  47. package/dist/reflect/tool/recording/delete-previous-step.js +3 -8
  48. package/dist/reflect/tool/recording/get-screenshot.js +4 -14
  49. package/dist/reflect/tool/suites/cancel-suite-execution.js +4 -14
  50. package/dist/reflect/tool/suites/execute-suite.js +3 -8
  51. package/dist/reflect/tool/suites/get-suite-execution-status.js +4 -14
  52. package/dist/reflect/tool/suites/list-suite-executions.js +3 -8
  53. package/dist/reflect/tool/suites/list-suites.js +2 -1
  54. package/dist/reflect/tool/tests/get-test-status.js +3 -8
  55. package/dist/reflect/tool/tests/list-segments.js +5 -20
  56. package/dist/reflect/tool/tests/list-tests.js +2 -1
  57. package/dist/reflect/tool/tests/run-test.js +3 -8
  58. package/dist/swagger/client/api.js +11 -2
  59. package/dist/swagger/client/portal-types.js +0 -3
  60. package/dist/swagger/client/tools.js +0 -1
  61. package/dist/swagger/client.js +1 -1
  62. package/dist/zephyr/client.js +1 -1
  63. package/package.json +6 -4
@@ -0,0 +1,99 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOL_NAMES, ENDPOINTS, PAGINATION, RESPONSE_FIELDS } from "../../config/constants.js";
3
+ import { SetProjectContextResponse, SetProjectContextBody, GetProjectsResponse } from "../../schema/project.schema.js";
4
+ const PROJECT_SEARCH_MAX_RESULTS = 10;
5
+ const ERRORS = {
6
+ PROJECT_NOT_FOUND: (key) => `Project with key '${key}' not found. Use the get_projects tool to list available projects.`,
7
+ NO_EXACT_MATCH: (key, candidates) => `No exact match for project key '${key}'. Did you mean one of: ${candidates.join(", ")}? Please provide the exact project key.`
8
+ };
9
+ class SetProjectContext extends Tool {
10
+ specification = {
11
+ title: TOOL_NAMES.SET_PROJECT_CONTEXT.TITLE,
12
+ summary: TOOL_NAMES.SET_PROJECT_CONTEXT.SUMMARY,
13
+ readOnly: false,
14
+ idempotent: true,
15
+ inputSchema: SetProjectContextBody,
16
+ outputSchema: SetProjectContextResponse,
17
+ purpose: "Set the active QTM4J project for the current session. This tool MUST be called before performing ANY project-specific operation. NEVER auto-select a project — if the user has not specified a project key, call get_projects first, show the list to the user, and ask them to choose.",
18
+ useCases: [
19
+ "Set the active project at the start of a new conversation",
20
+ "Switch to a different project mid-conversation",
21
+ "Validate that a project key exists before performing operations",
22
+ "Establish project context required by all project-specific tools"
23
+ ],
24
+ examples: [
25
+ {
26
+ description: "Set SCRUM project as active",
27
+ parameters: { projectKey: "SCRUM" },
28
+ expectedOutput: "Project context set to SCRUM (ID: 10000)"
29
+ },
30
+ {
31
+ description: "Switch to AD project",
32
+ parameters: { projectKey: "AD" },
33
+ expectedOutput: "Project context switched to AD"
34
+ }
35
+ ],
36
+ hints: [
37
+ "CRITICAL: This tool MUST be called before ANY project-specific tool.",
38
+ "NEVER auto-select a project. If the user does not specify a project key, call get_projects first, present the list to the user and ask them to choose. Do NOT pick one on their behalf.",
39
+ "The project key must be an exact match (e.g., 'SCRUM', not 'scrum project').",
40
+ "After calling this tool, use the availableFields in the response to map user input via NLP (e.g. user says 'Major' → send 'High', user says 'Critical' → send 'Blocker').",
41
+ "Switching projects clears the cached field metadata of the previous project only.",
42
+ "If this tool is called again in the same session, it resets the context to the new project."
43
+ ],
44
+ outputDescription: "JSON object with projectId, projectKey, projectName, confirmation message, and availableFields. availableFields contains priority and status options for NLP mapping in subsequent tool calls."
45
+ };
46
+ handle = async (rawArgs) => {
47
+ const { projectKey } = SetProjectContextBody.parse(rawArgs);
48
+ const apiClient = this.client.getApiClient();
49
+ const body = {
50
+ [RESPONSE_FIELDS.START_AT]: PAGINATION.DEFAULT_START_AT,
51
+ [RESPONSE_FIELDS.MAX_RESULTS]: PROJECT_SEARCH_MAX_RESULTS,
52
+ search: projectKey
53
+ };
54
+ const response = await apiClient.post(ENDPOINTS.PROJECTS, body);
55
+ const parsed = GetProjectsResponse.parse(response);
56
+ if (parsed.data.length === 0) {
57
+ throw new ToolError(ERRORS.PROJECT_NOT_FOUND(projectKey));
58
+ }
59
+ const exactMatch = parsed.data.find(
60
+ (p) => p.key.toLowerCase() === projectKey.toLowerCase()
61
+ );
62
+ if (!exactMatch) {
63
+ const candidates = parsed.data.map((p) => `${p.key} (${p.name})`);
64
+ throw new ToolError(ERRORS.NO_EXACT_MATCH(projectKey, candidates));
65
+ }
66
+ const fieldResolver = this.client.getResolverRegistry();
67
+ try {
68
+ const previousContext = fieldResolver.requireProjectContext();
69
+ if (previousContext.projectKey !== exactMatch.key) {
70
+ fieldResolver.clearProjectCache(previousContext.projectKey);
71
+ }
72
+ } catch {
73
+ }
74
+ fieldResolver.setProjectContext({
75
+ projectId: exactMatch.id,
76
+ projectKey: exactMatch.key,
77
+ projectName: exactMatch.name
78
+ });
79
+ let availableFields = {};
80
+ try {
81
+ availableFields = await fieldResolver.getCommonAttributeResolver().preload(exactMatch.key, exactMatch.id);
82
+ } catch {
83
+ }
84
+ const result = {
85
+ projectId: exactMatch.id,
86
+ projectKey: exactMatch.key,
87
+ projectName: exactMatch.name,
88
+ message: `Project context set to '${exactMatch.key}' (${exactMatch.name}, ID: ${exactMatch.id}). You can now perform project-specific operations.`,
89
+ availableFields
90
+ };
91
+ return {
92
+ structuredContent: SetProjectContextResponse.parse(result),
93
+ content: []
94
+ };
95
+ };
96
+ }
97
+ export {
98
+ SetProjectContext
99
+ };
@@ -0,0 +1,113 @@
1
+ import { Tool } 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 { CreateTestCaseResponse, CreateTestCaseBody } from "../../schema/test-case.schema.js";
5
+ const FIELD_CONFIG = {
6
+ [InputField.PRIORITY]: ResolverKeys.CommonAttribute.PRIORITY,
7
+ [InputField.STATUS]: ResolverKeys.CommonAttribute.TESTCASE_STATUS,
8
+ [InputField.FOLDER]: ResolverKeys.CommonAttribute.TESTCASE_FOLDER,
9
+ [InputField.COMPONENTS]: ResolverKeys.SearchableField.COMPONENTS,
10
+ [InputField.LABELS]: ResolverKeys.SearchableField.LABEL
11
+ };
12
+ class CreateTestCase extends Tool {
13
+ // ─── Tool Specification ────────────────────────────────────────────────────
14
+ specification = {
15
+ title: TOOL_NAMES.CREATE_TEST_CASE.TITLE,
16
+ summary: TOOL_NAMES.CREATE_TEST_CASE.SUMMARY,
17
+ readOnly: false,
18
+ idempotent: false,
19
+ inputSchema: CreateTestCaseBody,
20
+ outputSchema: CreateTestCaseResponse,
21
+ purpose: "Create a new test case in QTM4J with steps, metadata, and field auto-resolution. For priority and status, use the names returned by set_project_context and map via NLP. For labels and components, provide exact names — resolved on demand. PREREQUISITE: set_project_context must be called before this tool.",
22
+ useCases: [
23
+ "Create a basic test case with just a summary",
24
+ "Create a test case with priority and status using names from set_project_context response",
25
+ "Create a test case with labels and components by exact name",
26
+ "Add detailed test steps with step descriptions, test data, and expected results",
27
+ "Create a test case in a specific folder using folderId",
28
+ "Set assignee and reporter using Jira account IDs",
29
+ "Create test cases for manual testing with step-by-step instructions",
30
+ "Create test cases with all metadata fields for comprehensive test management"
31
+ ],
32
+ examples: [
33
+ {
34
+ description: "Create a simple test case (project must be set via set_project_context first)",
35
+ parameters: { summary: "Search Functionality" },
36
+ expectedOutput: "Test case created with key 'SCRUM-TC-xxx'"
37
+ },
38
+ {
39
+ description: "Create a test case with priority and status",
40
+ parameters: {
41
+ summary: "Search Functionality",
42
+ description: "Verify search functionality works correctly",
43
+ priority: "High",
44
+ status: "To Do"
45
+ },
46
+ expectedOutput: "Test case created with resolved priority and status IDs"
47
+ },
48
+ {
49
+ description: "Create a test case with labels, components, and steps",
50
+ parameters: {
51
+ summary: "Search Functionality",
52
+ description: "Search Functionality Test",
53
+ priority: "High",
54
+ status: "To Do",
55
+ labels: ["Release_1", "Sprint 1"],
56
+ components: ["UI", "Cloud"],
57
+ steps: [
58
+ {
59
+ stepDetails: "Enter a keyword in the search box",
60
+ testData: 'Keyword = "Test"',
61
+ expectedResult: "The keyword should be visible in the search box"
62
+ },
63
+ {
64
+ stepDetails: "Click on the Search button",
65
+ testData: "Click on Search Button",
66
+ expectedResult: "Search results matching the keyword should be displayed"
67
+ },
68
+ {
69
+ stepDetails: "Verify the search results",
70
+ testData: "Expected results list",
71
+ expectedResult: "Results should be relevant to the entered keyword"
72
+ }
73
+ ]
74
+ },
75
+ expectedOutput: "Test case created with resolved labels/components/priority/status and 3 steps"
76
+ }
77
+ ],
78
+ hints: [
79
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
80
+ "Priority and status values were returned by set_project_context. Use NLP to map user input (e.g., 'Major' → 'High', 'Critical' → 'Blocker').",
81
+ "If priority or status name is not found, the operation proceeds without that field and a warning is returned.",
82
+ "Labels and components are resolved on demand. If a name is not found, it is skipped with a warning.",
83
+ "Steps: ALWAYS include all three fields — stepDetails, testData, and expectedResult. Generate reasonable values if not provided.",
84
+ "folderId is optional. assignee and reporter accept Jira account IDs."
85
+ ],
86
+ outputDescription: "JSON object with test case ID, key, version number, and summary. Warnings included if any fields were skipped."
87
+ };
88
+ // ─── Handle Implementation ──────────────────────────────────────────────────
89
+ handle = async (rawArgs) => {
90
+ const fieldResolver = this.client.getResolverRegistry();
91
+ const context = fieldResolver.requireProjectContext();
92
+ const body = {
93
+ ...CreateTestCaseBody.parse(rawArgs),
94
+ projectId: String(context.projectId),
95
+ folderId: "MCP Generated"
96
+ };
97
+ const warnings = [];
98
+ await Promise.all(
99
+ Object.entries(FIELD_CONFIG).map(
100
+ ([inputField, resolverKey]) => fieldResolver.getResolver(resolverKey).resolve(inputField, resolverKey, body, context, warnings)
101
+ )
102
+ );
103
+ const response = await this.client.getApiClient().post(ENDPOINTS.CREATE_TEST_CASE, body);
104
+ const validated = CreateTestCaseResponse.parse(response);
105
+ return {
106
+ structuredContent: validated,
107
+ content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
108
+ };
109
+ };
110
+ }
111
+ export {
112
+ CreateTestCase
113
+ };
@@ -0,0 +1,295 @@
1
+ import { Tool } from "../../../common/tools.js";
2
+ import { TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
3
+ import { SearchTestCaseResponse, SearchTestCaseBody } from "../../schema/get-test-case.schema.js";
4
+ class GetTestCases extends Tool {
5
+ specification = {
6
+ title: TOOL_NAMES.SEARCH_TEST_CASES.TITLE,
7
+ summary: TOOL_NAMES.SEARCH_TEST_CASES.SUMMARY,
8
+ readOnly: true,
9
+ idempotent: true,
10
+ inputSchema: SearchTestCaseBody,
11
+ outputSchema: SearchTestCaseResponse,
12
+ purpose: "Search and filter test cases in a QTM4J project. The filter object is sent in the request body; fields, sort, startAt, and maxResults are sent as URL query parameters. All filter values accept string names directly (e.g., status: ['Done'], priority: ['High'], labels: ['Release_1']). PREREQUISITE: set_project_context must be called before this tool.",
13
+ useCases: [
14
+ "Search all test cases in a project",
15
+ "Filter test cases by status (e.g., 'Done', 'To Do', 'In Progress')",
16
+ "Filter test cases by priority (e.g., 'High', 'Medium', 'Low')",
17
+ "Filter test cases by labels and components",
18
+ "Search test cases by text in summary and description",
19
+ "Filter test cases by assignee or reporter",
20
+ "Filter test cases by creation/update date ranges",
21
+ "Filter test cases by automation status",
22
+ "Request only specific fields to reduce response size",
23
+ "Sort results using the sort query param (e.g., 'created:desc', 'priority:asc')",
24
+ "Paginate through large result sets using startAt and maxResults",
25
+ "Combine multiple filters for complex queries",
26
+ "Find all failed test cases that need attention (by status and execution date)",
27
+ "Get test cases for sprint planning (filter by sprint and status)",
28
+ "Audit test coverage by searching for untested areas (filter by executed date)",
29
+ "Find all manual test cases assigned to a specific tester",
30
+ "Generate test reports by filtering and sorting test cases",
31
+ "Track test case changes over time (filter by update date range)",
32
+ "Identify high-priority test cases pending review",
33
+ "Search for test cases related to specific features (using searchText)",
34
+ "Find duplicate or similar test cases (using searchText)",
35
+ "Get automated vs manual test distribution (filter by isAutomated)",
36
+ "Monitor test execution trends (filter by executed date ranges)",
37
+ "Prepare test execution schedules (filter by assignee and priority)"
38
+ ],
39
+ examples: [
40
+ {
41
+ description: "Search all test cases in the project",
42
+ parameters: {},
43
+ expectedOutput: "Paginated list of all test cases with all fields (first 50 results)"
44
+ },
45
+ {
46
+ description: "Filter test cases by status",
47
+ parameters: {
48
+ filter: {
49
+ status: ["Done"]
50
+ }
51
+ },
52
+ expectedOutput: "List of test cases with 'Done' status"
53
+ },
54
+ {
55
+ description: "Filter by multiple statuses and priorities with specific fields",
56
+ parameters: {
57
+ filter: {
58
+ status: ["Done", "To Do"],
59
+ priority: ["High", "Medium"]
60
+ },
61
+ fields: ["key", "summary", "status", "priority", "assignee"]
62
+ },
63
+ expectedOutput: "Test cases matching the filters with only the selected fields returned"
64
+ },
65
+ {
66
+ description: "Search test cases by text in summary/description",
67
+ parameters: {
68
+ filter: {
69
+ searchText: "login functionality"
70
+ }
71
+ },
72
+ expectedOutput: "Test cases containing 'login functionality' in summary or description"
73
+ },
74
+ {
75
+ description: "Filter by labels and components",
76
+ parameters: {
77
+ filter: {
78
+ labels: ["Release_1", "Sprint 1"],
79
+ components: ["UI", "Cloud"]
80
+ }
81
+ },
82
+ expectedOutput: "Test cases tagged with the specified labels and components"
83
+ },
84
+ {
85
+ description: "Filter by assignee and automation status",
86
+ parameters: {
87
+ filter: {
88
+ assignee: ["712020:ddc8e24b-2de7-404b-b9ed-3d7b241e2ced"],
89
+ isAutomated: false
90
+ }
91
+ },
92
+ expectedOutput: "Manual test cases assigned to the specified user"
93
+ },
94
+ {
95
+ description: "Filter by creation date range",
96
+ parameters: {
97
+ filter: {
98
+ createdOnFrom: "01/Jan/2026",
99
+ createdOnTo: "31/Dec/2026"
100
+ }
101
+ },
102
+ expectedOutput: "Test cases created during 2026"
103
+ },
104
+ {
105
+ description: "Paginate and sort by creation date (newest first)",
106
+ parameters: {
107
+ filter: {
108
+ status: ["Done"]
109
+ },
110
+ startAt: 0,
111
+ maxResults: 50,
112
+ sort: "created:desc"
113
+ },
114
+ expectedOutput: "First 50 'Done' test cases sorted by creation date, newest first"
115
+ },
116
+ {
117
+ description: "Get all available fields for test cases",
118
+ parameters: {
119
+ filter: {},
120
+ fields: [
121
+ "key",
122
+ "summary",
123
+ "description",
124
+ "priority",
125
+ "status",
126
+ "assignee",
127
+ "isAutomated",
128
+ "reporter",
129
+ "estimatedTime",
130
+ "labels",
131
+ "components",
132
+ "fixVersions",
133
+ "sprint",
134
+ "folders",
135
+ "updated",
136
+ "created",
137
+ "executed",
138
+ "flakyScore",
139
+ "passRateScore",
140
+ "aiGenerated",
141
+ "precondition",
142
+ "orderNo",
143
+ "seqNo",
144
+ "version"
145
+ ]
146
+ },
147
+ expectedOutput: "Test cases with all available fields explicitly requested"
148
+ },
149
+ {
150
+ description: "Filter by folder and fix version",
151
+ parameters: {
152
+ filter: {
153
+ folders: [123, 456],
154
+ fixVersions: [789]
155
+ }
156
+ },
157
+ expectedOutput: "Test cases in the specified folders and fix versions"
158
+ },
159
+ {
160
+ description: "Complex filter: multiple criteria combined with multi-field sort",
161
+ parameters: {
162
+ filter: {
163
+ status: ["Done", "In Progress"],
164
+ priority: ["High"],
165
+ labels: ["Release_1"],
166
+ isAutomated: false,
167
+ createdOnFrom: "01/Apr/2026",
168
+ createdOnTo: "30/Apr/2026"
169
+ },
170
+ fields: ["key", "summary", "status", "priority", "created"],
171
+ sort: "priority:asc,created:desc"
172
+ },
173
+ expectedOutput: "High-priority manual test cases created in April 2026 with 'Done' or 'In Progress' status, sorted by priority ascending then creation date descending"
174
+ },
175
+ {
176
+ description: "Find all automated test cases for CI/CD pipeline",
177
+ parameters: {
178
+ filter: {
179
+ isAutomated: true,
180
+ status: ["Done", "In Progress"]
181
+ },
182
+ fields: ["key", "summary", "status", "labels", "components"]
183
+ },
184
+ expectedOutput: "Up to 50 automated test cases ready for execution in CI/CD"
185
+ },
186
+ {
187
+ description: "Sprint planning: get pending test cases for a team, sorted by priority",
188
+ parameters: {
189
+ filter: {
190
+ status: ["To Do", "In Progress"],
191
+ assignee: [
192
+ "712020:ddc8e24b-2de7-404b-b9ed-3d7b241e2ced",
193
+ "712020:b8479b55-6d23-478c-a2ad-4c8ce176e1fc"
194
+ ],
195
+ priority: ["High", "Medium"]
196
+ },
197
+ fields: [
198
+ "key",
199
+ "summary",
200
+ "status",
201
+ "priority",
202
+ "assignee",
203
+ "estimatedTime"
204
+ ],
205
+ sort: "priority:desc"
206
+ },
207
+ expectedOutput: "Pending high and medium priority test cases assigned to the team, sorted by priority descending"
208
+ },
209
+ {
210
+ description: "Test coverage report: find completed cases sorted oldest first",
211
+ parameters: {
212
+ filter: {
213
+ status: ["Done"]
214
+ },
215
+ fields: ["key", "summary", "priority", "created", "assignee"],
216
+ sort: "created:asc"
217
+ },
218
+ expectedOutput: "Completed test cases sorted by creation date, oldest first"
219
+ },
220
+ {
221
+ description: "Find test cases by keyword for regression testing",
222
+ parameters: {
223
+ filter: {
224
+ searchText: "authentication login",
225
+ status: ["Done"]
226
+ },
227
+ fields: ["key", "summary", "description", "labels", "components"]
228
+ },
229
+ expectedOutput: "All completed test cases related to authentication/login functionality"
230
+ },
231
+ {
232
+ description: "Weekly test execution summary",
233
+ parameters: {
234
+ filter: {
235
+ executedOnFrom: "27/Apr/2026",
236
+ executedOnTo: "03/May/2026"
237
+ },
238
+ fields: [
239
+ "key",
240
+ "summary",
241
+ "status",
242
+ "executed",
243
+ "passRateScore",
244
+ "flakyScore"
245
+ ],
246
+ sort: "executed:desc"
247
+ },
248
+ expectedOutput: "Test cases executed in the past week with their pass rates and flaky scores, sorted most-recent first"
249
+ }
250
+ ],
251
+ hints: [
252
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
253
+ "REQUEST STRUCTURE: filter goes in the request body; fields, sort, startAt, and maxResults are URL query parameters.",
254
+ "The 'projectId' inside filter is auto-populated from the active project context if not provided.",
255
+ "All filter values accept string names directly — no ID resolution needed (e.g., status: ['Done'], priority: ['High']).",
256
+ "FIELDS: Pass as an array (sent as comma-separated URL param). Example: { fields: ['key', 'summary', 'status'] }. Omit to return all fields.",
257
+ "Available fields: key, summary, description, priority, status, assignee, isAutomated, reporter, estimatedTime, labels, components, fixVersions, sprint, folders, updated, created, executed, flakyScore, passRateScore, aiGenerated, precondition, orderNo, seqNo, version",
258
+ "SORTING: Use 'sort' with format 'fieldName:order' (asc/desc). Multiple fields: 'priority:asc,created:desc'. Sortable fields: key, summary, created, updated, status, priority, executed.",
259
+ "PAGINATION: startAt (default: 0) and maxResults (default: 50, max: 50) are sent as URL query params. Increment startAt by 50 to get the next page. Stop when startAt >= total.",
260
+ "Date format for all filter date fields: 'dd/MMM/yyyy' (e.g., '17/Apr/2026', '01/Jan/2026'). Case-sensitive.",
261
+ "FILTER LOGIC: Multiple values within one filter field use OR (status: ['Done', 'To Do'] = Done OR To Do).",
262
+ "FILTER LOGIC: Different filter fields are combined with AND (status + priority = both must match).",
263
+ "The 'searchText' filter searches both summary and description fields (case-insensitive). To get details of a specific test case by its key (e.g., 'SCRUM-TC-145'), pass the key as filter.searchText — there is no separate key filter field.",
264
+ "For assignee/reporter filters, use Jira account IDs (format: '712020:uuid'). Multiple IDs = OR logic.",
265
+ "Omitting filter entirely returns all test cases in the active project (paginated)."
266
+ ],
267
+ outputDescription: "JSON object with total (total matching test cases), startAt, maxResults, and data (array of test case objects for this page)."
268
+ };
269
+ handle = async (rawArgs) => {
270
+ const args = SearchTestCaseBody.parse(rawArgs);
271
+ const context = this.client.getResolverRegistry().requireProjectContext();
272
+ if (!args.filter) {
273
+ args.filter = {};
274
+ }
275
+ if (!args.filter.projectId) {
276
+ args.filter.projectId = String(context.projectId);
277
+ }
278
+ const params = new URLSearchParams();
279
+ if (args.fields?.length)
280
+ params.set(RESPONSE_FIELDS.FIELDS, args.fields.join(","));
281
+ if (args.sort) params.set(RESPONSE_FIELDS.SORT, args.sort);
282
+ params.set(RESPONSE_FIELDS.START_AT, String(args.startAt));
283
+ params.set(RESPONSE_FIELDS.MAX_RESULTS, String(args.maxResults));
284
+ const endpoint = `${ENDPOINTS.SEARCH_TEST_CASES}?${params.toString()}`;
285
+ const response = await this.client.getApiClient().post(endpoint, { filter: args.filter });
286
+ const validated = SearchTestCaseResponse.parse(response);
287
+ return {
288
+ structuredContent: validated,
289
+ content: []
290
+ };
291
+ };
292
+ }
293
+ export {
294
+ GetTestCases
295
+ };
@@ -0,0 +1,111 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
3
+ import { ResolverKeys } from "../../config/field-resolution.types.js";
4
+ import { GetTestStepsResponse, GetTestStepsBody } from "../../schema/get-test-steps.schema.js";
5
+ class GetTestSteps extends Tool {
6
+ specification = {
7
+ title: TOOL_NAMES.GET_TEST_STEPS.TITLE,
8
+ summary: TOOL_NAMES.GET_TEST_STEPS.SUMMARY,
9
+ readOnly: true,
10
+ idempotent: true,
11
+ inputSchema: GetTestStepsBody,
12
+ outputSchema: GetTestStepsResponse,
13
+ purpose: "Retrieve the test steps for a specific test case version using the human-readable test case key. The key is automatically resolved to the internal test case UID via a dedicated batch API — no separate lookup required. Test steps describe the step-by-step actions (stepDetails), input data (testData), and expected results (expectedResult) for executing a test case. Steps may also reference shared (reusable) test cases, surfaced via the 'shareable' field. PREREQUISITE: set_project_context must be called before this tool.",
14
+ useCases: [
15
+ "View all steps of a test case before executing it",
16
+ "Review steps for a specific test case version",
17
+ "Filter steps by action text, test data, or expected result",
18
+ "Get steps for a test case found via search_test_cases",
19
+ "Inspect shared (reusable) steps embedded in a test case",
20
+ "Sort steps by sequence number to view them in execution order",
21
+ "Paginate through test cases that have a large number of steps"
22
+ ],
23
+ examples: [
24
+ {
25
+ description: "Get all steps for a test case (latest version)",
26
+ parameters: { key: "SCRUM-TC-145" },
27
+ expectedOutput: "All steps for SCRUM-TC-145 with stepDetails, testData, expectedResult, and any shared step blocks"
28
+ },
29
+ {
30
+ description: "Get steps for a specific version",
31
+ parameters: { key: "SCRUM-TC-145", versionNo: 2 },
32
+ expectedOutput: "Steps for version 2 of SCRUM-TC-145"
33
+ },
34
+ {
35
+ description: "Get steps in execution order",
36
+ parameters: { key: "SCRUM-TC-85", sort: "seqNo:asc" },
37
+ expectedOutput: "All steps sorted by sequence number ascending"
38
+ },
39
+ {
40
+ description: "Filter steps by action text",
41
+ parameters: {
42
+ key: "SCRUM-TC-32",
43
+ filter: { stepDetails: "Open the application" }
44
+ },
45
+ expectedOutput: "Steps whose stepDetails contain 'Open the application'"
46
+ },
47
+ {
48
+ description: "Filter steps by expected result",
49
+ parameters: {
50
+ key: "SCRUM-TC-65",
51
+ filter: { expectedResult: "logged in successfully" }
52
+ },
53
+ expectedOutput: "Steps whose expectedResult contains 'logged in successfully'"
54
+ },
55
+ {
56
+ description: "Filter steps by test data",
57
+ parameters: {
58
+ key: "SCRUM-TC-125",
59
+ filter: { testData: "Username: user1" }
60
+ },
61
+ expectedOutput: "Steps with testData containing 'Username: user1'"
62
+ },
63
+ {
64
+ description: "Paginate through many steps",
65
+ parameters: {
66
+ key: "SCRUM-TC-105",
67
+ startAt: 0,
68
+ maxResults: 10,
69
+ sort: "seqNo:asc"
70
+ },
71
+ expectedOutput: "First 10 steps in sequence order"
72
+ }
73
+ ],
74
+ hints: [
75
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
76
+ "KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'. PROJECT_KEY is the Jira project key; the number is the test case counter within that project (auto-incremented, not the same as seqNo).",
77
+ "VERSION: versionNo defaults to the latest version. Get the version number from search_test_cases response: version.versionNo.",
78
+ "Use search_test_cases to discover test case keys before calling this tool.",
79
+ "SHAREABLE STEPS: Steps with a non-null 'shareable' field are references to shared/reusable test cases. The 'shareable.shareableTestSteps' array contains the embedded sub-steps with decimal seqNo values (e.g. '1.1', '1.2').",
80
+ "FILTER: Each filter field is a substring match (case-insensitive). Multiple fields combine with AND.",
81
+ "SORT: 'seqNo:asc' shows steps in their natural execution order. Allowed sort fields: stepDetails, testData, seqNo, expectedResult.",
82
+ "PAGINATION: startAt and maxResults are URL query params. Default page size is 50, maximum is 100."
83
+ ],
84
+ outputDescription: "JSON object with total (total matching steps), startAt, maxResults, and data (array of step objects). Each step has: id, seqNo, stepDetails, testData, expectedResult, attachmentCount. Shared steps also have a 'shareable' object containing shareableTestcaseUID and shareableTestSteps array."
85
+ };
86
+ handle = async (rawArgs) => {
87
+ const args = GetTestStepsBody.parse(rawArgs);
88
+ const fieldResolver = this.client.getResolverRegistry();
89
+ const context = fieldResolver.requireProjectContext();
90
+ const resolved = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.key]);
91
+ const entry = resolved[args.key];
92
+ if (!entry) {
93
+ throw new ToolError(
94
+ `Test case '${args.key}' not found in project '${context.projectKey}'. Verify the key using the search_test_cases tool.`
95
+ );
96
+ }
97
+ const versionNo = args.versionNo ?? entry.latestVersion;
98
+ const params = new URLSearchParams();
99
+ params.set(RESPONSE_FIELDS.START_AT, String(args.startAt));
100
+ params.set(RESPONSE_FIELDS.MAX_RESULTS, String(args.maxResults));
101
+ if (args.sort) params.set(RESPONSE_FIELDS.SORT, args.sort);
102
+ const endpoint = `${ENDPOINTS.TEST_STEPS(entry.uid, versionNo)}?${params.toString()}`;
103
+ const filterBody = args.filter && Object.keys(args.filter).length > 0 ? { filter: args.filter } : {};
104
+ const response = await this.client.getApiClient().post(endpoint, filterBody);
105
+ const validated = GetTestStepsResponse.parse(response);
106
+ return { structuredContent: validated, content: [] };
107
+ };
108
+ }
109
+ export {
110
+ GetTestSteps
111
+ };