@smartbear/mcp 0.23.0 → 0.25.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 (148) hide show
  1. package/README.md +10 -2
  2. package/dist/bearq/client.js +12 -13
  3. package/dist/bearq/tool/tasks/chat-with-qa-lead.js +1 -0
  4. package/dist/bearq/tool/tasks/expand-application-model.js +1 -0
  5. package/dist/bearq/tool/tasks/get-task-status.js +1 -0
  6. package/dist/bearq/tool/tasks/get-task.js +1 -0
  7. package/dist/bearq/tool/tasks/refine-all-draft-tests.js +1 -0
  8. package/dist/bearq/tool/tasks/refine-test-cases.js +1 -0
  9. package/dist/bearq/tool/tasks/refine-tests-in-functional-areas.js +1 -0
  10. package/dist/bearq/tool/tasks/run-regression-tests.js +1 -0
  11. package/dist/bearq/tool/tasks/run-test-cases.js +1 -0
  12. package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +1 -0
  13. package/dist/bearq/tool/tasks/stop-task.js +1 -0
  14. package/dist/bearq/tool/tasks/wait-for-task.js +1 -0
  15. package/dist/bugsnag/client.js +24 -44
  16. package/dist/bugsnag/tool/error/get-error.js +1 -0
  17. package/dist/bugsnag/tool/error/list-project-errors.js +1 -0
  18. package/dist/bugsnag/tool/error/update-error.js +1 -0
  19. package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +1 -0
  20. package/dist/bugsnag/tool/event/get-event.js +1 -0
  21. package/dist/bugsnag/tool/event/list-error-events.js +1 -0
  22. package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +1 -0
  23. package/dist/bugsnag/tool/performance/get-span-group.js +1 -0
  24. package/dist/bugsnag/tool/performance/get-trace.js +1 -0
  25. package/dist/bugsnag/tool/performance/list-span-groups.js +1 -0
  26. package/dist/bugsnag/tool/performance/list-spans.js +1 -0
  27. package/dist/bugsnag/tool/performance/list-trace-fields.js +1 -0
  28. package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +1 -0
  29. package/dist/bugsnag/tool/project/get-current-project.js +1 -0
  30. package/dist/bugsnag/tool/project/list-project-event-filters.js +1 -0
  31. package/dist/bugsnag/tool/project/list-projects.js +1 -0
  32. package/dist/bugsnag/tool/release/get-build.js +1 -0
  33. package/dist/bugsnag/tool/release/get-release.js +1 -0
  34. package/dist/bugsnag/tool/release/list-releases.js +1 -0
  35. package/dist/collaborator/client.js +24 -19
  36. package/dist/common/client-registry.js +63 -29
  37. package/dist/common/server.js +57 -1
  38. package/dist/common/transport-http.js +90 -69
  39. package/dist/common/transport-stdio.js +29 -24
  40. package/dist/package.json.js +1 -1
  41. package/dist/pactflow/client/base.js +3 -3
  42. package/dist/pactflow/client/tools.js +102 -0
  43. package/dist/pactflow/client.js +30 -43
  44. package/dist/qmetry/client/tools/automation-tools.js +2 -0
  45. package/dist/qmetry/client/tools/issue-tools.js +6 -0
  46. package/dist/qmetry/client/tools/project-tools.js +9 -0
  47. package/dist/qmetry/client/tools/requirement-tools.js +5 -0
  48. package/dist/qmetry/client/tools/testcase-tools.js +7 -0
  49. package/dist/qmetry/client/tools/testsuite-tools.js +11 -0
  50. package/dist/qmetry/client.js +20 -18
  51. package/dist/qtm4j/client.js +57 -23
  52. package/dist/qtm4j/config/constants.js +197 -5
  53. package/dist/qtm4j/config/field-resolution.types.js +5 -2
  54. package/dist/qtm4j/http/api-client.js +90 -3
  55. package/dist/qtm4j/resolver/cache/cache.js +1 -1
  56. package/dist/qtm4j/resolver/resolver-registry.js +7 -1
  57. package/dist/qtm4j/resolver/resolvers/common-attribute-resolver.js +1 -0
  58. package/dist/qtm4j/resolver/resolvers/component-resolver.js +2 -0
  59. package/dist/qtm4j/resolver/resolvers/label-resolver.js +2 -0
  60. package/dist/qtm4j/resolver/resolvers/requirement-id-resolver.js +28 -0
  61. package/dist/qtm4j/resolver/resolvers/test-cycle-uid-resolver.js +28 -0
  62. package/dist/qtm4j/schema/automation.schema.js +107 -0
  63. package/dist/qtm4j/schema/linked-items.schema.js +95 -0
  64. package/dist/qtm4j/schema/requirements.schema.js +109 -0
  65. package/dist/qtm4j/schema/search-test-cycle.schema.js +133 -0
  66. package/dist/qtm4j/schema/test-cycle.link.schema.js +260 -0
  67. package/dist/qtm4j/schema/test-cycle.schema.js +39 -0
  68. package/dist/qtm4j/schema/update-test-cycle.schema.js +54 -0
  69. package/dist/qtm4j/tool/project/get-projects.js +2 -1
  70. package/dist/qtm4j/tool/project/set-project-context.js +2 -1
  71. package/dist/qtm4j/tool/requirement/get-linked-testcases.js +93 -0
  72. package/dist/qtm4j/tool/requirement/link-testcases.js +107 -0
  73. package/dist/qtm4j/tool/requirement/unlink-testcases.js +97 -0
  74. package/dist/qtm4j/tool/test-automation/get-automation-history.js +70 -0
  75. package/dist/qtm4j/tool/test-automation/upload-automation-result.js +144 -0
  76. package/dist/qtm4j/tool/test-case/create-test-case.js +2 -1
  77. package/dist/qtm4j/tool/test-case/get-linked-requirements.js +67 -0
  78. package/dist/qtm4j/tool/test-case/get-test-cases.js +2 -1
  79. package/dist/qtm4j/tool/test-case/get-test-steps.js +2 -1
  80. package/dist/qtm4j/tool/test-case/link-requirements.js +124 -0
  81. package/dist/qtm4j/tool/test-case/unlink-requirements.js +116 -0
  82. package/dist/qtm4j/tool/test-case/update-test-case.js +2 -1
  83. package/dist/qtm4j/tool/test-cycle/create-test-cycle.js +81 -0
  84. package/dist/qtm4j/tool/test-cycle/get-linked-requirements.js +71 -0
  85. package/dist/qtm4j/tool/test-cycle/link-requirements.js +91 -0
  86. package/dist/qtm4j/tool/test-cycle/link-testcases.js +118 -0
  87. package/dist/qtm4j/tool/test-cycle/search-linked-testcases.js +114 -0
  88. package/dist/qtm4j/tool/test-cycle/search-test-cycle.js +137 -0
  89. package/dist/qtm4j/tool/test-cycle/unlink-requirements.js +87 -0
  90. package/dist/qtm4j/tool/test-cycle/unlink-testcases.js +103 -0
  91. package/dist/qtm4j/tool/test-cycle/update-test-cycle.js +162 -0
  92. package/dist/reflect/client.js +15 -24
  93. package/dist/reflect/config/constants.js +0 -2
  94. package/dist/reflect/tool/recording/add-prompt-step.js +1 -0
  95. package/dist/reflect/tool/recording/add-segment.js +1 -0
  96. package/dist/reflect/tool/recording/connect-to-session.js +1 -0
  97. package/dist/reflect/tool/recording/delete-previous-step.js +1 -0
  98. package/dist/reflect/tool/recording/get-screenshot.js +1 -0
  99. package/dist/reflect/tool/suites/cancel-suite-execution.js +1 -0
  100. package/dist/reflect/tool/suites/execute-suite.js +1 -0
  101. package/dist/reflect/tool/suites/get-suite-execution-status.js +1 -0
  102. package/dist/reflect/tool/suites/list-suite-executions.js +1 -0
  103. package/dist/reflect/tool/suites/list-suites.js +1 -0
  104. package/dist/reflect/tool/tests/get-test-status.js +1 -0
  105. package/dist/reflect/tool/tests/list-segments.js +1 -0
  106. package/dist/reflect/tool/tests/list-tests.js +1 -0
  107. package/dist/reflect/tool/tests/run-test.js +1 -0
  108. package/dist/swagger/client/portal-types.js +1 -1
  109. package/dist/swagger/client/tools.js +23 -0
  110. package/dist/swagger/client.js +25 -28
  111. package/dist/zephyr/client.js +14 -21
  112. package/dist/zephyr/tool/environment/get-environments.js +1 -0
  113. package/dist/zephyr/tool/folder/create-folder.js +1 -0
  114. package/dist/zephyr/tool/issue-link/get-test-cases.js +1 -0
  115. package/dist/zephyr/tool/issue-link/get-test-cycles.js +1 -0
  116. package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -0
  117. package/dist/zephyr/tool/priority/get-priorities.js +1 -0
  118. package/dist/zephyr/tool/project/get-project.js +1 -0
  119. package/dist/zephyr/tool/project/get-projects.js +1 -0
  120. package/dist/zephyr/tool/status/get-statuses.js +1 -0
  121. package/dist/zephyr/tool/test-case/create-issue-link.js +1 -0
  122. package/dist/zephyr/tool/test-case/create-test-case.js +1 -0
  123. package/dist/zephyr/tool/test-case/create-test-script.js +1 -0
  124. package/dist/zephyr/tool/test-case/create-test-steps.js +1 -0
  125. package/dist/zephyr/tool/test-case/create-web-link.js +1 -0
  126. package/dist/zephyr/tool/test-case/get-links.js +1 -0
  127. package/dist/zephyr/tool/test-case/get-test-case.js +1 -0
  128. package/dist/zephyr/tool/test-case/get-test-cases.js +1 -0
  129. package/dist/zephyr/tool/test-case/get-test-script.js +1 -0
  130. package/dist/zephyr/tool/test-case/get-test-steps.js +1 -0
  131. package/dist/zephyr/tool/test-case/update-test-case.js +1 -0
  132. package/dist/zephyr/tool/test-cycle/create-issue-link.js +1 -0
  133. package/dist/zephyr/tool/test-cycle/create-test-cycle.js +1 -0
  134. package/dist/zephyr/tool/test-cycle/create-web-link.js +1 -0
  135. package/dist/zephyr/tool/test-cycle/get-links.js +1 -0
  136. package/dist/zephyr/tool/test-cycle/get-test-cycle.js +1 -0
  137. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +1 -0
  138. package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
  139. package/dist/zephyr/tool/test-execution/create-issue-link.js +1 -0
  140. package/dist/zephyr/tool/test-execution/create-test-execution.js +1 -0
  141. package/dist/zephyr/tool/test-execution/get-test-execution-links.js +1 -0
  142. package/dist/zephyr/tool/test-execution/get-test-execution.js +1 -0
  143. package/dist/zephyr/tool/test-execution/get-test-executions.js +1 -0
  144. package/dist/zephyr/tool/test-execution/get-test-steps.js +1 -0
  145. package/dist/zephyr/tool/test-execution/update-test-execution.js +1 -0
  146. package/dist/zephyr/tool/test-execution/update-test-steps.js +1 -0
  147. package/package.json +1 -1
  148. package/dist/common/request-context.js +0 -20
@@ -0,0 +1,118 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
3
+ import { ResolverKeys } from "../../config/field-resolution.types.js";
4
+ import { TestCycleLinkResponse, LinkTestCasesToCycleBody } from "../../schema/test-cycle.link.schema.js";
5
+ class LinkTestCasesToCycle extends Tool {
6
+ specification = {
7
+ title: TOOL_NAMES.LINK_TESTCASES_TO_CYCLE.TITLE,
8
+ summary: TOOL_NAMES.LINK_TESTCASES_TO_CYCLE.SUMMARY,
9
+ readOnly: false,
10
+ idempotent: false,
11
+ toolset: TOOLSETS.TEST_CYCLES,
12
+ inputSchema: LinkTestCasesToCycleBody,
13
+ outputSchema: TestCycleLinkResponse,
14
+ purpose: "Link test cases to a QTM4J test cycle using the cycle's human-readable key. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Test cases can be specified by their human-readable keys (e.g. 'SCRUM-TC-1'), which are also resolved to internal IDs and latest versions automatically. Alternatively, use a filter object to select test cases by criteria such as status, priority, labels, or folder. The active project ID is injected automatically into the filter. Test cases that cannot be linked are reported in warnings. PREREQUISITE: set_project_context must be called before this tool.",
15
+ useCases: [
16
+ "Add specific test cases to a test cycle by key",
17
+ "Populate a test cycle with test cases matching a filter (e.g., all High priority test cases)",
18
+ "Link test cases from a specific folder to a test cycle",
19
+ "Add test cases to a cycle with a specific assignee or environment"
20
+ ],
21
+ examples: [
22
+ {
23
+ description: "Link two test cases by key",
24
+ parameters: {
25
+ cycleKey: "SCRUM-TR-1",
26
+ testCaseKeys: ["SCRUM-TC-10", "SCRUM-TC-11"]
27
+ },
28
+ expectedOutput: "Test cases linked to test cycle"
29
+ },
30
+ {
31
+ description: "Link test cases matching a filter with assignee",
32
+ parameters: {
33
+ cycleKey: "SCRUM-TR-1",
34
+ filter: { priority: ["High"], status: ["To Do"] },
35
+ assignee: "5b10a2844c20165700ede21f",
36
+ startNewExecution: true
37
+ },
38
+ expectedOutput: "Filtered test cases linked to cycle with assignee"
39
+ },
40
+ {
41
+ description: "Link test cases in a folder to a cycle with planned date",
42
+ parameters: {
43
+ cycleKey: "SCRUM-TR-5",
44
+ filter: { folderId: 42, withChild: true },
45
+ executionPlannedDate: "2024-03-31",
46
+ sort: "key:asc"
47
+ },
48
+ expectedOutput: "Folder test cases linked to cycle with planned date"
49
+ }
50
+ ],
51
+ hints: [
52
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
53
+ "CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
54
+ "TEST CASE KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
55
+ "Provide either testCaseKeys or filter — not both.",
56
+ "projectId in filter is auto-filled from the active project context — do not set it manually.",
57
+ "filter.excludeCycleId excludes test cases already in another cycle.",
58
+ "startNewExecution: true creates a fresh execution record for each linked test case.",
59
+ "executionPlannedDate must be in yyyy-MM-dd format (e.g., '2024-03-31').",
60
+ "actualTime must be in HH:mm format (e.g., '02:30').",
61
+ "If a test case key cannot be resolved, it is reported in warnings and others are still linked."
62
+ ],
63
+ outputDescription: "Confirmation with the cycle key and linked: true. Warnings included if any test cases could not be resolved or linked."
64
+ };
65
+ handle = async (rawArgs) => {
66
+ const args = LinkTestCasesToCycleBody.parse(rawArgs);
67
+ const fieldResolver = this.client.getResolverRegistry();
68
+ const context = fieldResolver.requireProjectContext();
69
+ const warnings = [];
70
+ const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
71
+ const cycleEntry = cycleMap[args.cycleKey];
72
+ if (!cycleEntry) {
73
+ throw new ToolError(
74
+ `Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
75
+ );
76
+ }
77
+ const body = {};
78
+ if (args.testCaseKeys?.length) {
79
+ const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, args.testCaseKeys);
80
+ const testCases = [];
81
+ for (const tcKey of args.testCaseKeys) {
82
+ const resolved = uidMap[tcKey];
83
+ if (resolved) {
84
+ testCases.push({
85
+ id: resolved.uid,
86
+ versionNo: resolved.latestVersion
87
+ });
88
+ } else {
89
+ warnings.push(
90
+ `Test case '${tcKey}' could not be resolved and was skipped.`
91
+ );
92
+ }
93
+ }
94
+ if (testCases.length > 0) body.testCases = testCases;
95
+ }
96
+ if (args.filter) {
97
+ body.filter = { ...args.filter, projectId: context.projectId };
98
+ }
99
+ const {
100
+ cycleKey: _cycleKey,
101
+ testCaseKeys: _tcKeys,
102
+ filter: _filter,
103
+ ...rest
104
+ } = args;
105
+ Object.assign(body, rest);
106
+ await this.client.getApiClient().post(ENDPOINTS.LINK_TESTCASES_TO_CYCLE(cycleEntry.uid), body);
107
+ return {
108
+ structuredContent: TestCycleLinkResponse.parse({
109
+ cycleKey: args.cycleKey,
110
+ linked: true
111
+ }),
112
+ content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
113
+ };
114
+ };
115
+ }
116
+ export {
117
+ LinkTestCasesToCycle
118
+ };
@@ -0,0 +1,114 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOLSETS, TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
3
+ import { ResolverKeys } from "../../config/field-resolution.types.js";
4
+ import { SearchLinkedTestCasesInCycleResponse, SearchLinkedTestCasesInCycleBody } from "../../schema/test-cycle.link.schema.js";
5
+ class SearchLinkedTestCasesInCycle extends Tool {
6
+ specification = {
7
+ title: TOOL_NAMES.SEARCH_LINKED_TESTCASES_IN_CYCLE.TITLE,
8
+ summary: TOOL_NAMES.SEARCH_LINKED_TESTCASES_IN_CYCLE.SUMMARY,
9
+ readOnly: true,
10
+ idempotent: true,
11
+ toolset: TOOLSETS.TEST_CYCLES,
12
+ inputSchema: SearchLinkedTestCasesInCycleBody,
13
+ outputSchema: SearchLinkedTestCasesInCycleResponse,
14
+ purpose: "Search and filter test case executions linked to a QTM4J test cycle. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Supports pagination, field selection, sorting, and rich filter criteria such as execution result, priority, status, environment, assignee, and date ranges. The active project ID is injected automatically into the filter. PREREQUISITE: set_project_context must be called before this tool.",
15
+ useCases: [
16
+ "List all test cases linked to a test cycle",
17
+ "Find failed or blocked test case executions in a cycle",
18
+ "Search for test cases in a cycle by execution result (Pass, Fail, Blocked)",
19
+ "Filter test cases in a cycle by priority or status",
20
+ "Retrieve test cases assigned to a specific executor in a cycle",
21
+ "Get test cases with defects in a cycle",
22
+ "Paginate through large test cycle execution lists",
23
+ "Request only specific fields to reduce response size"
24
+ ],
25
+ examples: [
26
+ {
27
+ description: "List all test cases in a cycle",
28
+ parameters: { cycleKey: "SCRUM-TR-1" },
29
+ expectedOutput: "Paginated list of test case executions in the cycle (first 50 results)"
30
+ },
31
+ {
32
+ description: "Find failed test cases in a cycle",
33
+ parameters: {
34
+ cycleKey: "SCRUM-TR-1",
35
+ filter: { executionResult: ["Fail"] },
36
+ fields: ["key", "summary", "executionResult", "priority"]
37
+ },
38
+ expectedOutput: "Test case executions with Fail result"
39
+ },
40
+ {
41
+ description: "Search with pagination and sort",
42
+ parameters: {
43
+ cycleKey: "SCRUM-TR-5",
44
+ maxResults: 25,
45
+ startAt: 0,
46
+ sort: "key:asc",
47
+ filter: { status: ["To Do"] }
48
+ },
49
+ expectedOutput: "First 25 To Do test cases in the cycle sorted by key ascending"
50
+ },
51
+ {
52
+ description: "Filter by execution assignee and environment",
53
+ parameters: {
54
+ cycleKey: "SCRUM-TR-2",
55
+ filter: {
56
+ executionAssignee: ["5b10a2844c20165700ede21f"],
57
+ environment: ["Staging"]
58
+ },
59
+ fields: [
60
+ "key",
61
+ "summary",
62
+ "executionResult",
63
+ "environment",
64
+ "actualTime"
65
+ ]
66
+ },
67
+ expectedOutput: "Test cases assigned to the specified user in the Staging environment"
68
+ }
69
+ ],
70
+ hints: [
71
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
72
+ "CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
73
+ "projectId in filter is auto-filled from the active project context — do not set it manually.",
74
+ "fields is sent as a query parameter; filter is sent in the request body.",
75
+ "Allowed fields: id, key, summary, description, executionResult, status, priority, environment, tcWithDefects, estimatedTime, actualTime, createdOn, updatedOn, sprint, seqNo, latestTcExecutionId, customFields, flakyScore, passRateScore.",
76
+ "Allowed sort fields: id, key, summary, description, executionResult, status, priority, environment, tcWithDefects, estimatedTime, actualTime, createdOn, updatedOn, sprint, flakyScore, passRateScore.",
77
+ "Date range format for filter fields: 'dd/mmm/yyyy,dd/mmm/yyyy' (e.g., '01/Jan/2024,31/Mar/2024').",
78
+ "maxResults defaults to 50, maximum is 100. Use startAt to paginate.",
79
+ "executionResult filter accepts values like 'Pass', 'Fail', 'Blocked', 'Unexecuted'."
80
+ ],
81
+ outputDescription: "JSON object with total (total matching executions), startAt, maxResults, and data (array of test case execution objects for this page)."
82
+ };
83
+ handle = async (rawArgs) => {
84
+ const args = SearchLinkedTestCasesInCycleBody.parse(rawArgs);
85
+ const fieldResolver = this.client.getResolverRegistry();
86
+ const context = fieldResolver.requireProjectContext();
87
+ const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
88
+ const cycleEntry = cycleMap[args.cycleKey];
89
+ if (!cycleEntry) {
90
+ throw new ToolError(
91
+ `Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
92
+ );
93
+ }
94
+ const params = new URLSearchParams();
95
+ if (args.fields?.length) {
96
+ params.set(RESPONSE_FIELDS.FIELDS, args.fields.join(","));
97
+ }
98
+ if (args.sort) {
99
+ params.set(RESPONSE_FIELDS.SORT, args.sort);
100
+ }
101
+ params.set(RESPONSE_FIELDS.MAX_RESULTS, String(args.maxResults));
102
+ params.set(RESPONSE_FIELDS.START_AT, String(args.startAt));
103
+ const endpoint = `${ENDPOINTS.SEARCH_LINKED_TESTCASES_IN_CYCLE(cycleEntry.uid)}?${params.toString()}`;
104
+ const filter = args.filter ? { ...args.filter, projectId: [context.projectId] } : { projectId: [context.projectId] };
105
+ const response = await this.client.getApiClient().post(endpoint, { filter });
106
+ return {
107
+ structuredContent: SearchLinkedTestCasesInCycleResponse.parse(response),
108
+ content: []
109
+ };
110
+ };
111
+ }
112
+ export {
113
+ SearchLinkedTestCasesInCycle
114
+ };
@@ -0,0 +1,137 @@
1
+ import { Tool } from "../../../common/tools.js";
2
+ import { TOOL_NAMES, TOOLSETS, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
3
+ import { SearchTestCycleResponse, SearchTestCycleBody } from "../../schema/search-test-cycle.schema.js";
4
+ class SearchTestCycles extends Tool {
5
+ specification = {
6
+ title: TOOL_NAMES.SEARCH_TEST_CYCLES.TITLE,
7
+ toolset: TOOLSETS.TEST_CYCLES,
8
+ summary: TOOL_NAMES.SEARCH_TEST_CYCLES.SUMMARY,
9
+ readOnly: true,
10
+ idempotent: true,
11
+ inputSchema: SearchTestCycleBody,
12
+ outputSchema: SearchTestCycleResponse,
13
+ purpose: "Search and filter test cycles in a QTM4J project. projectId is auto-injected from the active project context — do not provide it.",
14
+ useCases: [
15
+ "Find test cycles by status, priority, assignee, reporter, or folder",
16
+ "Find test cycles by planned execution date range (plannedStartDate / plannedEndDate)",
17
+ "Find test cycles created or updated within a date range (createdOn / updatedOn)",
18
+ "Search test cycles by keyword across key, summary, and description",
19
+ "Paginate, sort, and select specific response fields"
20
+ ],
21
+ examples: [
22
+ {
23
+ description: "Find all in-progress and to-do cycles",
24
+ parameters: { filter: { status: ["In Progress", "To Do"] } },
25
+ expectedOutput: "Paginated list of matching test cycles"
26
+ },
27
+ {
28
+ description: "Find cycles owned by a specific user",
29
+ parameters: { filter: { assignee: ["5b10a2844c20165700ede21f"] } },
30
+ expectedOutput: "Test cycles assigned to that user"
31
+ },
32
+ {
33
+ description: "Find cycles with planned start date in a range, requesting date fields explicitly",
34
+ parameters: {
35
+ filter: { plannedStartDate: "01/Apr/2026,30/Apr/2026" },
36
+ fields: [
37
+ "key",
38
+ "summary",
39
+ "status",
40
+ "assignee",
41
+ "plannedStartDate",
42
+ "plannedEndDate"
43
+ ]
44
+ },
45
+ expectedOutput: "Cycles with planned start date in April 2026 including date fields"
46
+ },
47
+ {
48
+ description: "Keyword search with sort, pagination, and selected fields",
49
+ parameters: {
50
+ filter: { searchText: "regression" },
51
+ fields: ["key", "summary", "status", "assignee"],
52
+ sort: "plannedStartDate:asc",
53
+ startAt: 0,
54
+ maxResults: 25
55
+ },
56
+ expectedOutput: "Cycles matching 'regression', sorted by planned start date"
57
+ },
58
+ {
59
+ description: "Find cycles created last week",
60
+ parameters: {
61
+ filter: { createdOn: "01/May/2026,07/May/2026" },
62
+ fields: ["key", "summary", "status", "assignee"],
63
+ sort: "key:asc"
64
+ },
65
+ expectedOutput: "Test cycles created between 01 May and 07 May 2026"
66
+ },
67
+ {
68
+ description: "Find high-priority cycles updated recently by reporter",
69
+ parameters: {
70
+ filter: {
71
+ priority: ["High"],
72
+ reporter: ["5b10a2844c20165700ede21f"],
73
+ updatedOn: "01/May/2026,21/May/2026"
74
+ },
75
+ fields: ["key", "summary", "status", "priority", "assignee"]
76
+ },
77
+ expectedOutput: "High-priority cycles updated in May 2026 reported by that user"
78
+ },
79
+ {
80
+ description: "All filters combined with explicit field selection",
81
+ parameters: {
82
+ filter: {
83
+ status: ["In Progress"],
84
+ priority: ["High", "Medium"],
85
+ assignee: ["5b10a2844c20165700ede21f"],
86
+ folderId: 109987,
87
+ plannedStartDate: "02/Apr/2026,15/May/2026",
88
+ searchText: "regression"
89
+ },
90
+ fields: [
91
+ "key",
92
+ "summary",
93
+ "status",
94
+ "priority",
95
+ "assignee",
96
+ "plannedStartDate"
97
+ ],
98
+ sort: "plannedStartDate:asc",
99
+ maxResults: 25
100
+ },
101
+ expectedOutput: "Test cycles matching all specified filters with selected fields"
102
+ }
103
+ ],
104
+ hints: [
105
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
106
+ "SUPPORTED FILTER FIELDS: status, priority, assignee, reporter, folderId, labels, components, plannedStartDate, plannedEndDate, searchText, createdOn, updatedOn, isAutomated, aiGenerated. Do NOT use any other filter field names.",
107
+ "DATE FILTERS: createdOn = creation date; updatedOn = last-updated date; plannedStartDate / plannedEndDate = planned execution window. Format: 'dd/MMM/yyyy,dd/MMM/yyyy' e.g. '01/May/2026,21/May/2026'. Month is case-sensitive. 'Created last week' → createdOn, NOT plannedStartDate.",
108
+ "FIELDS: Pass as an array to select what to return. plannedStartDate and plannedEndDate are NOT in the default response — include them explicitly. Available: key, summary, description, status, priority, assignee, reporter, isAutomated, plannedStartDate, plannedEndDate, labels, components, fixVersions, sprint, defectCount, estimatedTime, actualTime, created, updated.",
109
+ "REQUEST STRUCTURE: filter → request body; fields, sort, startAt, maxResults → URL query params.",
110
+ "SORT: Allowed fields: key, summary, status, plannedStartDate, plannedEndDate, defectCount. Format: 'fieldName:asc' or 'fieldName:desc' e.g. 'plannedStartDate:asc'.",
111
+ "FOLDER ID: folderId in fields.testCycle and fields.testCase is a numeric ID. Tell the user they can get it by right-clicking the target folder in QTM4J and selecting 'Copy Folder Id'. Always ask the user for the numeric ID directly — never try to look it up."
112
+ ],
113
+ outputDescription: "JSON object with total (matching cycles across all pages), startAt, maxResults, and data (array of test cycle objects for this page). Each item always has id and key. Other fields depend on what was requested via the fields parameter."
114
+ };
115
+ handle = async (rawArgs) => {
116
+ const args = SearchTestCycleBody.parse(rawArgs);
117
+ const context = this.client.getResolverRegistry().requireProjectContext();
118
+ if (!args.filter) args.filter = {};
119
+ args.filter.projectId = context.projectId;
120
+ const params = new URLSearchParams();
121
+ if (args.fields?.length)
122
+ params.set(RESPONSE_FIELDS.FIELDS, args.fields.join(","));
123
+ params.set(RESPONSE_FIELDS.START_AT, String(args.startAt));
124
+ params.set(RESPONSE_FIELDS.MAX_RESULTS, String(args.maxResults));
125
+ params.set(RESPONSE_FIELDS.SORT, args.sort);
126
+ const endpoint = `${ENDPOINTS.SEARCH_TEST_CYCLES}?${params.toString()}`;
127
+ const response = await this.client.getApiClient().post(endpoint, { filter: args.filter });
128
+ const validated = SearchTestCycleResponse.parse(response);
129
+ return {
130
+ structuredContent: validated,
131
+ content: []
132
+ };
133
+ };
134
+ }
135
+ export {
136
+ SearchTestCycles
137
+ };
@@ -0,0 +1,87 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
3
+ import { ResolverKeys } from "../../config/field-resolution.types.js";
4
+ import { UnlinkRequirementsFromCycleResponse, UnlinkRequirementsFromCycleBody } from "../../schema/test-cycle.link.schema.js";
5
+ class UnlinkRequirementsFromCycle extends Tool {
6
+ specification = {
7
+ title: TOOL_NAMES.UNLINK_REQUIREMENTS_FROM_CYCLE.TITLE,
8
+ summary: TOOL_NAMES.UNLINK_REQUIREMENTS_FROM_CYCLE.SUMMARY,
9
+ readOnly: false,
10
+ idempotent: false,
11
+ toolset: TOOLSETS.TEST_CYCLES,
12
+ inputSchema: UnlinkRequirementsFromCycleBody,
13
+ outputSchema: UnlinkRequirementsFromCycleResponse,
14
+ purpose: "Unlink Jira requirements from a QTM4J test cycle using the cycle's human-readable key. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Requirements can be specified by their human-readable keys (e.g. 'SCRUM-1') which are resolved to internal IDs automatically, or set unLinkAll to true to remove all requirements from the cycle in one call. Requirements that cannot be unlinked are reported as warnings. PREREQUISITE: set_project_context must be called before this tool.",
15
+ useCases: [
16
+ "Remove one or more Jira requirements from a test cycle by key",
17
+ "Unlink all requirements from a test cycle at once",
18
+ "Clean up requirement links before repopulating a test cycle"
19
+ ],
20
+ examples: [
21
+ {
22
+ description: "Unlink two requirements by key",
23
+ parameters: {
24
+ cycleKey: "SCRUM-TR-1",
25
+ requirementKeys: ["SCRUM-1", "SCRUM-2"]
26
+ },
27
+ expectedOutput: "Requirements SCRUM-1 and SCRUM-2 unlinked from test cycle"
28
+ },
29
+ {
30
+ description: "Unlink all requirements from a cycle",
31
+ parameters: { cycleKey: "SCRUM-TR-1", unLinkAll: true },
32
+ expectedOutput: "All requirements unlinked from test cycle"
33
+ }
34
+ ],
35
+ hints: [
36
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
37
+ "CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
38
+ "Requirement keys follow Jira issue key format: '{PROJECT_KEY}-{number}' (e.g. 'SCRUM-1').",
39
+ "Provide either requirementKeys or unLinkAll: true — not both.",
40
+ "unLinkAll: true removes every requirement from the cycle — no need to list them individually.",
41
+ "If a requirement key cannot be resolved or unlinked, it is reported in warnings and other requirements are still unlinked."
42
+ ],
43
+ outputDescription: "Confirmation with the cycle key and unlinked: true. Warnings are included if any requirements could not be resolved or unlinked."
44
+ };
45
+ handle = async (rawArgs) => {
46
+ const args = UnlinkRequirementsFromCycleBody.parse(rawArgs);
47
+ const fieldResolver = this.client.getResolverRegistry();
48
+ const context = fieldResolver.requireProjectContext();
49
+ const warnings = [];
50
+ const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
51
+ const cycleEntry = cycleMap[args.cycleKey];
52
+ if (!cycleEntry) {
53
+ throw new ToolError(
54
+ `Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
55
+ );
56
+ }
57
+ const body = {};
58
+ if (args.unLinkAll) {
59
+ body.unLinkAll = true;
60
+ } else if (args.requirementKeys?.length) {
61
+ const reqMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.REQUIREMENT_KEY_TO_ID).resolveAndReturn(context.projectId, args.requirementKeys);
62
+ const requirementIds = [];
63
+ for (const reqKey of args.requirementKeys) {
64
+ const resolved = reqMap[reqKey];
65
+ if (resolved) {
66
+ requirementIds.push(Number(resolved.id));
67
+ } else {
68
+ warnings.push(
69
+ `Requirement '${reqKey}' could not be resolved and was skipped.`
70
+ );
71
+ }
72
+ }
73
+ if (requirementIds.length > 0) body.requirementIds = requirementIds;
74
+ }
75
+ await this.client.getApiClient().post(ENDPOINTS.UNLINK_REQUIREMENTS_FROM_CYCLE(cycleEntry.uid), body);
76
+ return {
77
+ structuredContent: UnlinkRequirementsFromCycleResponse.parse({
78
+ cycleKey: args.cycleKey,
79
+ unlinked: true
80
+ }),
81
+ content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
82
+ };
83
+ };
84
+ }
85
+ export {
86
+ UnlinkRequirementsFromCycle
87
+ };
@@ -0,0 +1,103 @@
1
+ import { Tool, ToolError } from "../../../common/tools.js";
2
+ import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
3
+ import { ResolverKeys } from "../../config/field-resolution.types.js";
4
+ import { TestCycleLinkResponse, UnlinkTestCasesFromCycleBody } from "../../schema/test-cycle.link.schema.js";
5
+ class UnlinkTestCasesFromCycle extends Tool {
6
+ specification = {
7
+ title: TOOL_NAMES.UNLINK_TESTCASES_FROM_CYCLE.TITLE,
8
+ summary: TOOL_NAMES.UNLINK_TESTCASES_FROM_CYCLE.SUMMARY,
9
+ readOnly: false,
10
+ idempotent: false,
11
+ toolset: TOOLSETS.TEST_CYCLES,
12
+ inputSchema: UnlinkTestCasesFromCycleBody,
13
+ outputSchema: TestCycleLinkResponse,
14
+ purpose: "Unlink test cases from a QTM4J test cycle using the cycle's human-readable key. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Test cases can be specified by their human-readable keys (e.g. 'SCRUM-TC-1'), which are also resolved to internal IDs and latest versions automatically. Alternatively, set unlinkAll to true to remove all test cases from the cycle in one call, or use a filter to select which test cases to unlink by criteria. The active project ID is injected automatically into the filter. PREREQUISITE: set_project_context must be called before this tool.",
15
+ useCases: [
16
+ "Remove specific test cases from a test cycle by key",
17
+ "Unlink all test cases from a test cycle at once",
18
+ "Remove test cases matching a filter from a cycle (e.g., all 'Done' test cases)",
19
+ "Clean up a test cycle before repopulating it"
20
+ ],
21
+ examples: [
22
+ {
23
+ description: "Unlink two specific test cases",
24
+ parameters: {
25
+ cycleKey: "SCRUM-TR-1",
26
+ testCaseKeys: ["SCRUM-TC-10", "SCRUM-TC-11"]
27
+ },
28
+ expectedOutput: "Test cases unlinked from test cycle"
29
+ },
30
+ {
31
+ description: "Unlink all test cases from a cycle",
32
+ parameters: { cycleKey: "SCRUM-TR-1", unlinkAll: true },
33
+ expectedOutput: "All test cases unlinked from cycle"
34
+ },
35
+ {
36
+ description: "Unlink test cases matching a status filter",
37
+ parameters: {
38
+ cycleKey: "SCRUM-TR-5",
39
+ filter: { status: ["Done"], labels: ["Deprecated"] }
40
+ },
41
+ expectedOutput: "Filtered test cases unlinked from cycle"
42
+ }
43
+ ],
44
+ hints: [
45
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
46
+ "CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
47
+ "TEST CASE KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
48
+ "Provide exactly one of: testCaseKeys, unlinkAll, or filter.",
49
+ "unlinkAll: true removes every test case from the cycle — no need to list them individually.",
50
+ "projectId in filter is auto-filled from the active project context — do not set it manually.",
51
+ "If a test case key cannot be resolved, it is reported in warnings and others are still unlinked."
52
+ ],
53
+ outputDescription: "Confirmation with the cycle key and unlinked: true. Warnings included if any test cases could not be resolved or unlinked."
54
+ };
55
+ handle = async (rawArgs) => {
56
+ const args = UnlinkTestCasesFromCycleBody.parse(rawArgs);
57
+ const fieldResolver = this.client.getResolverRegistry();
58
+ const context = fieldResolver.requireProjectContext();
59
+ const warnings = [];
60
+ const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
61
+ const cycleEntry = cycleMap[args.cycleKey];
62
+ if (!cycleEntry) {
63
+ throw new ToolError(
64
+ `Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
65
+ );
66
+ }
67
+ const body = {};
68
+ if (args.unlinkAll) {
69
+ body.unlinkAll = true;
70
+ } else if (args.testCaseKeys?.length) {
71
+ const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, args.testCaseKeys);
72
+ const testCases = [];
73
+ for (const tcKey of args.testCaseKeys) {
74
+ const resolved = uidMap[tcKey];
75
+ if (resolved) {
76
+ testCases.push({
77
+ id: resolved.uid,
78
+ versionNo: resolved.latestVersion
79
+ });
80
+ } else {
81
+ warnings.push(
82
+ `Test case '${tcKey}' could not be resolved and was skipped.`
83
+ );
84
+ }
85
+ }
86
+ if (testCases.length > 0) body.testCases = testCases;
87
+ }
88
+ if (args.filter) {
89
+ body.filter = { ...args.filter, projectId: context.projectId };
90
+ }
91
+ await this.client.getApiClient().delete(ENDPOINTS.UNLINK_TESTCASES_FROM_CYCLE(cycleEntry.uid), body);
92
+ return {
93
+ structuredContent: TestCycleLinkResponse.parse({
94
+ cycleKey: args.cycleKey,
95
+ unlinked: true
96
+ }),
97
+ content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
98
+ };
99
+ };
100
+ }
101
+ export {
102
+ UnlinkTestCasesFromCycle
103
+ };