@smartbear/mcp 0.24.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.
- package/dist/bearq/client.js +12 -13
- package/dist/bearq/tool/tasks/chat-with-qa-lead.js +1 -0
- package/dist/bearq/tool/tasks/expand-application-model.js +1 -0
- package/dist/bearq/tool/tasks/get-task-status.js +1 -0
- package/dist/bearq/tool/tasks/get-task.js +1 -0
- package/dist/bearq/tool/tasks/refine-all-draft-tests.js +1 -0
- package/dist/bearq/tool/tasks/refine-test-cases.js +1 -0
- package/dist/bearq/tool/tasks/refine-tests-in-functional-areas.js +1 -0
- package/dist/bearq/tool/tasks/run-regression-tests.js +1 -0
- package/dist/bearq/tool/tasks/run-test-cases.js +1 -0
- package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +1 -0
- package/dist/bearq/tool/tasks/stop-task.js +1 -0
- package/dist/bearq/tool/tasks/wait-for-task.js +1 -0
- package/dist/bugsnag/client.js +24 -44
- package/dist/bugsnag/tool/error/get-error.js +1 -0
- package/dist/bugsnag/tool/error/list-project-errors.js +1 -0
- package/dist/bugsnag/tool/error/update-error.js +1 -0
- package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +1 -0
- package/dist/bugsnag/tool/event/get-event.js +1 -0
- package/dist/bugsnag/tool/event/list-error-events.js +1 -0
- package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +1 -0
- package/dist/bugsnag/tool/performance/get-span-group.js +1 -0
- package/dist/bugsnag/tool/performance/get-trace.js +1 -0
- package/dist/bugsnag/tool/performance/list-span-groups.js +1 -0
- package/dist/bugsnag/tool/performance/list-spans.js +1 -0
- package/dist/bugsnag/tool/performance/list-trace-fields.js +1 -0
- package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +1 -0
- package/dist/bugsnag/tool/project/get-current-project.js +1 -0
- package/dist/bugsnag/tool/project/list-project-event-filters.js +1 -0
- package/dist/bugsnag/tool/project/list-projects.js +1 -0
- package/dist/bugsnag/tool/release/get-build.js +1 -0
- package/dist/bugsnag/tool/release/get-release.js +1 -0
- package/dist/bugsnag/tool/release/list-releases.js +1 -0
- package/dist/collaborator/client.js +24 -19
- package/dist/common/client-registry.js +63 -29
- package/dist/common/server.js +57 -1
- package/dist/common/transport-http.js +90 -69
- package/dist/common/transport-stdio.js +29 -24
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/tools.js +102 -0
- package/dist/pactflow/client.js +30 -43
- package/dist/qmetry/client/tools/automation-tools.js +2 -0
- package/dist/qmetry/client/tools/issue-tools.js +6 -0
- package/dist/qmetry/client/tools/project-tools.js +9 -0
- package/dist/qmetry/client/tools/requirement-tools.js +5 -0
- package/dist/qmetry/client/tools/testcase-tools.js +7 -0
- package/dist/qmetry/client/tools/testsuite-tools.js +11 -0
- package/dist/qmetry/client.js +20 -18
- package/dist/qtm4j/client.js +42 -32
- package/dist/qtm4j/config/constants.js +101 -1
- package/dist/qtm4j/config/field-resolution.types.js +3 -1
- package/dist/qtm4j/http/api-client.js +20 -2
- package/dist/qtm4j/resolver/resolver-registry.js +7 -1
- package/dist/qtm4j/resolver/resolvers/requirement-id-resolver.js +28 -0
- package/dist/qtm4j/resolver/resolvers/test-cycle-uid-resolver.js +28 -0
- package/dist/qtm4j/schema/linked-items.schema.js +95 -0
- package/dist/qtm4j/schema/requirements.schema.js +109 -0
- package/dist/qtm4j/schema/test-cycle.link.schema.js +260 -0
- package/dist/qtm4j/tool/project/get-projects.js +2 -1
- package/dist/qtm4j/tool/project/set-project-context.js +2 -1
- package/dist/qtm4j/tool/requirement/get-linked-testcases.js +93 -0
- package/dist/qtm4j/tool/requirement/link-testcases.js +107 -0
- package/dist/qtm4j/tool/requirement/unlink-testcases.js +97 -0
- package/dist/qtm4j/tool/test-automation/get-automation-history.js +2 -1
- package/dist/qtm4j/tool/test-automation/upload-automation-result.js +2 -1
- package/dist/qtm4j/tool/test-case/create-test-case.js +2 -1
- package/dist/qtm4j/tool/test-case/get-linked-requirements.js +67 -0
- package/dist/qtm4j/tool/test-case/get-test-cases.js +2 -1
- package/dist/qtm4j/tool/test-case/get-test-steps.js +2 -1
- package/dist/qtm4j/tool/test-case/link-requirements.js +124 -0
- package/dist/qtm4j/tool/test-case/unlink-requirements.js +116 -0
- package/dist/qtm4j/tool/test-case/update-test-case.js +2 -1
- package/dist/qtm4j/tool/test-cycle/create-test-cycle.js +2 -1
- package/dist/qtm4j/tool/test-cycle/get-linked-requirements.js +71 -0
- package/dist/qtm4j/tool/test-cycle/link-requirements.js +91 -0
- package/dist/qtm4j/tool/test-cycle/link-testcases.js +118 -0
- package/dist/qtm4j/tool/test-cycle/search-linked-testcases.js +114 -0
- package/dist/qtm4j/tool/test-cycle/search-test-cycle.js +2 -1
- package/dist/qtm4j/tool/test-cycle/unlink-requirements.js +87 -0
- package/dist/qtm4j/tool/test-cycle/unlink-testcases.js +103 -0
- package/dist/qtm4j/tool/test-cycle/update-test-cycle.js +2 -1
- package/dist/reflect/client.js +15 -24
- package/dist/reflect/config/constants.js +0 -2
- package/dist/reflect/tool/recording/add-prompt-step.js +1 -0
- package/dist/reflect/tool/recording/add-segment.js +1 -0
- package/dist/reflect/tool/recording/connect-to-session.js +1 -0
- package/dist/reflect/tool/recording/delete-previous-step.js +1 -0
- package/dist/reflect/tool/recording/get-screenshot.js +1 -0
- package/dist/reflect/tool/suites/cancel-suite-execution.js +1 -0
- package/dist/reflect/tool/suites/execute-suite.js +1 -0
- package/dist/reflect/tool/suites/get-suite-execution-status.js +1 -0
- package/dist/reflect/tool/suites/list-suite-executions.js +1 -0
- package/dist/reflect/tool/suites/list-suites.js +1 -0
- package/dist/reflect/tool/tests/get-test-status.js +1 -0
- package/dist/reflect/tool/tests/list-segments.js +1 -0
- package/dist/reflect/tool/tests/list-tests.js +1 -0
- package/dist/reflect/tool/tests/run-test.js +1 -0
- package/dist/swagger/client/tools.js +23 -0
- package/dist/swagger/client.js +25 -28
- package/dist/zephyr/client.js +14 -21
- package/dist/zephyr/tool/environment/get-environments.js +1 -0
- package/dist/zephyr/tool/folder/create-folder.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-cases.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-cycles.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -0
- package/dist/zephyr/tool/priority/get-priorities.js +1 -0
- package/dist/zephyr/tool/project/get-project.js +1 -0
- package/dist/zephyr/tool/project/get-projects.js +1 -0
- package/dist/zephyr/tool/status/get-statuses.js +1 -0
- package/dist/zephyr/tool/test-case/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-case.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-script.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-steps.js +1 -0
- package/dist/zephyr/tool/test-case/create-web-link.js +1 -0
- package/dist/zephyr/tool/test-case/get-links.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-case.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-cases.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-script.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-steps.js +1 -0
- package/dist/zephyr/tool/test-case/update-test-case.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-web-link.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-links.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +1 -0
- package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-execution/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-execution/create-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-execution-links.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-executions.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-steps.js +1 -0
- package/dist/zephyr/tool/test-execution/update-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/update-test-steps.js +1 -0
- package/package.json +1 -1
- package/dist/common/request-context.js +0 -20
|
@@ -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
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Tool } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { SearchTestCycleResponse, SearchTestCycleBody } from "../../schema/search-test-cycle.schema.js";
|
|
4
4
|
class SearchTestCycles extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: TOOL_NAMES.SEARCH_TEST_CYCLES.TITLE,
|
|
7
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
7
8
|
summary: TOOL_NAMES.SEARCH_TEST_CYCLES.SUMMARY,
|
|
8
9
|
readOnly: true,
|
|
9
10
|
idempotent: true,
|
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { ResolverKeys, InputField } from "../../config/field-resolution.types.js";
|
|
4
4
|
import { UpdateTestCycleResponse, UpdateTestCycleBody } from "../../schema/update-test-cycle.schema.js";
|
|
5
5
|
const SIMPLE_FIELD_CONFIG = {
|
|
@@ -33,6 +33,7 @@ async function resolveAddDelete(resolver, inputField, resolverKey, field, contex
|
|
|
33
33
|
class UpdateTestCycle extends Tool {
|
|
34
34
|
specification = {
|
|
35
35
|
title: TOOL_NAMES.UPDATE_TEST_CYCLE.TITLE,
|
|
36
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
36
37
|
summary: TOOL_NAMES.UPDATE_TEST_CYCLE.SUMMARY,
|
|
37
38
|
readOnly: false,
|
|
38
39
|
idempotent: true,
|
package/dist/reflect/client.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
3
|
-
import { getRequestHeader } from "../common/request-context.js";
|
|
4
3
|
import { ToolError } from "../common/tools.js";
|
|
5
|
-
import { API_KEY_HEADER,
|
|
4
|
+
import { API_KEY_HEADER, AUTHORIZATION_HEADER } from "./config/constants.js";
|
|
6
5
|
import { SapTest } from "./prompt/sap-test.js";
|
|
7
6
|
import { AddPromptStep } from "./tool/recording/add-prompt-step.js";
|
|
8
7
|
import { AddSegment } from "./tool/recording/add-segment.js";
|
|
@@ -18,11 +17,12 @@ import { GetTestStatus } from "./tool/tests/get-test-status.js";
|
|
|
18
17
|
import { ListSegments } from "./tool/tests/list-segments.js";
|
|
19
18
|
import { ListTests } from "./tool/tests/list-tests.js";
|
|
20
19
|
import { RunTest } from "./tool/tests/run-test.js";
|
|
21
|
-
const ConfigurationSchema = z.object({
|
|
22
|
-
|
|
20
|
+
const ConfigurationSchema = z.object({});
|
|
21
|
+
const AuthenticationSchema = z.object({
|
|
22
|
+
api_token: z.string().describe("Reflect API authentication token").optional()
|
|
23
23
|
});
|
|
24
24
|
class ReflectClient {
|
|
25
|
-
|
|
25
|
+
_server;
|
|
26
26
|
activeConnections = /* @__PURE__ */ new Map();
|
|
27
27
|
sessionStates = /* @__PURE__ */ new Map();
|
|
28
28
|
mcpSessionConnections = /* @__PURE__ */ new Map();
|
|
@@ -30,33 +30,24 @@ class ReflectClient {
|
|
|
30
30
|
capabilityPrefix = "reflect";
|
|
31
31
|
configPrefix = "Reflect";
|
|
32
32
|
config = ConfigurationSchema;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
authenticationFields = AuthenticationSchema;
|
|
34
|
+
async configure(server, _config) {
|
|
35
|
+
this._server = server;
|
|
35
36
|
}
|
|
36
37
|
getAuthToken() {
|
|
37
|
-
|
|
38
|
-
if (contextHeader) {
|
|
39
|
-
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
40
|
-
if (token.startsWith("Bearer ")) {
|
|
41
|
-
token = token.substring(7);
|
|
42
|
-
}
|
|
43
|
-
return token;
|
|
44
|
-
}
|
|
45
|
-
return this._apiToken || null;
|
|
38
|
+
return this._server?.getEnv("api_token", this) || this._server?.getEnv(API_KEY_HEADER) || this._server?.getEnv(AUTHORIZATION_HEADER) || null;
|
|
46
39
|
}
|
|
47
40
|
isConfigured() {
|
|
48
|
-
return
|
|
41
|
+
return !!this._server;
|
|
42
|
+
}
|
|
43
|
+
hasAuth() {
|
|
44
|
+
return this.isConfigured() && !!this.getAuthToken();
|
|
49
45
|
}
|
|
50
46
|
isOAuthRequest() {
|
|
51
|
-
if (
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const authHeader = getRequestHeader(AUTHORIZATION_HEADER);
|
|
55
|
-
if (!authHeader) {
|
|
47
|
+
if (this._server?.getEnv("api_token", this) || this._server?.getEnv(API_KEY_HEADER)) {
|
|
56
48
|
return false;
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
return headerValue.toLowerCase().startsWith("bearer ");
|
|
50
|
+
return !!this._server?.getEnv(AUTHORIZATION_HEADER);
|
|
60
51
|
}
|
|
61
52
|
getAuthHeader() {
|
|
62
53
|
const token = this.getAuthToken();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const API_KEY_HEADER = "X-API-KEY";
|
|
2
|
-
const REFLECT_API_TOKEN_HEADER = "Reflect-Api-Token";
|
|
3
2
|
const AUTHORIZATION_HEADER = "Authorization";
|
|
4
3
|
const API_HOSTNAME = "api.reflect.run";
|
|
5
4
|
const WEBSOCKET_HOSTNAME = "recording.us-east-1.reflect.run";
|
|
@@ -8,7 +7,6 @@ export {
|
|
|
8
7
|
API_HOSTNAME,
|
|
9
8
|
API_KEY_HEADER,
|
|
10
9
|
AUTHORIZATION_HEADER,
|
|
11
|
-
REFLECT_API_TOKEN_HEADER,
|
|
12
10
|
WEBSOCKET_HOSTNAME,
|
|
13
11
|
WEB_APP_HOSTNAME
|
|
14
12
|
};
|
|
@@ -4,6 +4,7 @@ import { Tool, ToolError } from "../../../common/tools.js";
|
|
|
4
4
|
class AddPromptStep extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Add Prompt Step",
|
|
7
|
+
toolset: "Recording",
|
|
7
8
|
summary: "Add a natural language prompt step to an active Reflect recording session",
|
|
8
9
|
readOnly: false,
|
|
9
10
|
idempotent: false,
|
|
@@ -4,6 +4,7 @@ import { Tool, ToolError } from "../../../common/tools.js";
|
|
|
4
4
|
class AddSegment extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Add Segment",
|
|
7
|
+
toolset: "Recording",
|
|
7
8
|
summary: "Insert a reusable test segment into an active Reflect recording session",
|
|
8
9
|
readOnly: false,
|
|
9
10
|
idempotent: false,
|
|
@@ -5,6 +5,7 @@ import { WebSocketManager } from "../../websocket-manager.js";
|
|
|
5
5
|
class ConnectToSession extends Tool {
|
|
6
6
|
specification = {
|
|
7
7
|
title: "Connect To Session",
|
|
8
|
+
toolset: "Recording",
|
|
8
9
|
summary: `Connect to an active Reflect recording session via WebSocket to enable interactive control. When creating or editing a Reflect test using a connected recording session, follow these guidelines:
|
|
9
10
|
|
|
10
11
|
1. After connecting to a session, get the list of segments for the session's platform type so you know what actions could be added via segments vs needing to create new steps. Do not list tests, only list segments.
|
|
@@ -4,6 +4,7 @@ import { Tool, ToolError } from "../../../common/tools.js";
|
|
|
4
4
|
class DeletePreviousStep extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Delete Previous Step",
|
|
7
|
+
toolset: "Recording",
|
|
7
8
|
summary: "Delete the last step added to an active Reflect recording session",
|
|
8
9
|
readOnly: false,
|
|
9
10
|
idempotent: false,
|
|
@@ -4,6 +4,7 @@ import { Tool, ToolError } from "../../../common/tools.js";
|
|
|
4
4
|
class GetScreenshot extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Get Screenshot",
|
|
7
|
+
toolset: "Recording",
|
|
7
8
|
summary: "Capture a screenshot from the current state of an active Reflect recording session",
|
|
8
9
|
readOnly: true,
|
|
9
10
|
idempotent: true,
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class CancelSuiteExecution extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Cancel Suite Execution",
|
|
7
|
+
toolset: "Suites",
|
|
7
8
|
summary: "Cancel a reflect suite execution",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
suiteId: z.string().describe("ID of the reflect suite to cancel execution for"),
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class ExecuteSuite extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Execute Suite",
|
|
7
|
+
toolset: "Suites",
|
|
7
8
|
summary: "Execute a reflect suite",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
suiteId: z.string().describe("ID of the reflect suite to execute")
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class GetSuiteExecutionStatus extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Get Suite Execution Status",
|
|
7
|
+
toolset: "Suites",
|
|
7
8
|
summary: "Get the status of a reflect suite execution",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
suiteId: z.string().describe("ID of the reflect suite to get execution status for"),
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class ListSuiteExecutions extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "List Suite Executions",
|
|
7
|
+
toolset: "Suites",
|
|
7
8
|
summary: "List all executions for a given suite",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
suiteId: z.string().describe("ID of the reflect suite to list executions for")
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class GetTestStatus extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Get Test Status",
|
|
7
|
+
toolset: "Tests",
|
|
7
8
|
summary: "Get the status of a reflect test execution",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
executionId: z.string().describe("ID of the reflect test execution to get status for")
|
|
@@ -4,6 +4,7 @@ import { WEB_APP_HOSTNAME, API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class ListSegments extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "List Segments",
|
|
7
|
+
toolset: "Tests",
|
|
7
8
|
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
9
|
readOnly: true,
|
|
9
10
|
idempotent: true,
|
|
@@ -4,6 +4,7 @@ import { API_HOSTNAME } from "../../config/constants.js";
|
|
|
4
4
|
class RunTest extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: "Run Test",
|
|
7
|
+
toolset: "Tests",
|
|
7
8
|
summary: "Run a reflect test",
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
testId: z.string().describe("ID of the reflect test to run")
|