@smartbear/mcp 0.23.0 → 0.24.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.
@@ -0,0 +1,161 @@
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 { UpdateTestCycleResponse, UpdateTestCycleBody } from "../../schema/update-test-cycle.schema.js";
5
+ const SIMPLE_FIELD_CONFIG = {
6
+ [InputField.PRIORITY]: ResolverKeys.CommonAttribute.PRIORITY,
7
+ [InputField.STATUS]: ResolverKeys.CommonAttribute.TEST_CYCLE_STATUS
8
+ };
9
+ const ADD_DELETE_FIELD_CONFIG = {
10
+ [InputField.LABELS]: ResolverKeys.SearchableField.LABEL,
11
+ [InputField.COMPONENTS]: ResolverKeys.SearchableField.COMPONENTS
12
+ };
13
+ async function resolveAddDelete(resolver, inputField, resolverKey, field, context, warnings) {
14
+ const result = {};
15
+ for (const op of ["add", "delete"]) {
16
+ const names = field[op];
17
+ if (!names?.length) continue;
18
+ const tempBody = { [inputField]: names };
19
+ await resolver.resolve(
20
+ inputField,
21
+ resolverKey,
22
+ tempBody,
23
+ context,
24
+ warnings
25
+ );
26
+ const val = tempBody[inputField];
27
+ if (Array.isArray(val) && val.length > 0 && typeof val[0] === "number") {
28
+ result[op] = val;
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ class UpdateTestCycle extends Tool {
34
+ specification = {
35
+ title: TOOL_NAMES.UPDATE_TEST_CYCLE.TITLE,
36
+ summary: TOOL_NAMES.UPDATE_TEST_CYCLE.SUMMARY,
37
+ readOnly: false,
38
+ idempotent: true,
39
+ inputSchema: UpdateTestCycleBody,
40
+ outputSchema: UpdateTestCycleResponse,
41
+ purpose: "Update an existing test cycle in QTM4J by its human-readable key (e.g. 'SCRUM-TR-101'). Only provided fields are changed — omitted fields stay unchanged. Status and priority are auto-resolved from human-readable names. Labels and components use add/delete objects.",
42
+ useCases: [
43
+ "Update summary, status, priority, planned dates, assignee, or reporter",
44
+ "Clear a nullable field by passing null (e.g. description: null removes text, assignee: null unassigns owner)",
45
+ "Add or remove labels and components atomically without affecting other entries",
46
+ "Apply multiple field updates in a single call"
47
+ ],
48
+ examples: [
49
+ {
50
+ description: "Rename a test cycle",
51
+ parameters: {
52
+ key: "SCRUM-TR-101",
53
+ summary: "Regression Cycle - Sprint 12 Updated"
54
+ },
55
+ expectedOutput: "Test cycle updated with new summary"
56
+ },
57
+ {
58
+ description: "Change status and update planned dates",
59
+ parameters: {
60
+ key: "SCRUM-TR-101",
61
+ status: "In Progress",
62
+ plannedStartDate: "01/May/2026 09:00",
63
+ plannedEndDate: "31/May/2026 18:00"
64
+ },
65
+ expectedOutput: "Test cycle status and planned dates updated"
66
+ },
67
+ {
68
+ description: "Add a label and remove an old one",
69
+ parameters: {
70
+ key: "SCRUM-TR-101",
71
+ labels: { add: ["Regression", "Smoke"], delete: ["Sprint1"] }
72
+ },
73
+ expectedOutput: "Test cycle updated — Regression and Smoke labels added, Sprint1 removed"
74
+ },
75
+ {
76
+ description: "Clear the description text",
77
+ parameters: {
78
+ key: "SCRUM-TR-101",
79
+ description: null
80
+ },
81
+ expectedOutput: "Test cycle description cleared"
82
+ },
83
+ {
84
+ description: "Unassign the owner and clear planned dates",
85
+ parameters: {
86
+ key: "SCRUM-TR-101",
87
+ assignee: null,
88
+ plannedStartDate: null,
89
+ plannedEndDate: null
90
+ },
91
+ expectedOutput: "Test cycle owner unassigned and planned dates cleared"
92
+ },
93
+ {
94
+ description: "Full update with all fields",
95
+ parameters: {
96
+ key: "SCRUM-TR-101",
97
+ summary: "Final Regression Cycle",
98
+ description: "Updated for sprint 12.",
99
+ status: "In Progress",
100
+ priority: "High",
101
+ plannedStartDate: "15/May/2026 09:00",
102
+ plannedEndDate: "30/May/2026 18:00",
103
+ assignee: "5b10a2844c20165700ede21f",
104
+ labels: { add: ["Regression"], delete: ["Sprint1"] },
105
+ components: { add: ["Backend"], delete: ["Frontend"] }
106
+ },
107
+ expectedOutput: "Test cycle updated with all specified fields"
108
+ }
109
+ ],
110
+ hints: [
111
+ "PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
112
+ "KEY FORMAT: '{PROJECT_KEY}-TR-{number}' — e.g. 'SCRUM-TR-101'.",
113
+ "Pass explicit null to CLEAR a nullable field — e.g. description: null removes the description text, assignee: null unassigns the owner, plannedStartDate: null removes the date. Omitting a field leaves it unchanged.",
114
+ "Status and priority are auto-resolved from human-readable names loaded by set_project_context. If a name cannot be resolved, the cycle is still updated and a warning is returned.",
115
+ "Labels and components use add/delete — names are auto-resolved to IDs. Both operations can be combined in a single call.",
116
+ "Date format: 'dd/MMM/yyyy HH:mm' e.g. '15/May/2026 09:00'. Month must be capitalised (May not may or MAY).",
117
+ "Archived test cycles cannot be updated — the server returns 400. Unarchive first if needed."
118
+ ],
119
+ outputDescription: "Confirmation object with the test cycle key and updated: true. Warnings are included if any field names could not be resolved."
120
+ };
121
+ handle = async (rawArgs) => {
122
+ const args = UpdateTestCycleBody.parse(rawArgs);
123
+ const fieldResolver = this.client.getResolverRegistry();
124
+ const context = fieldResolver.requireProjectContext();
125
+ const warnings = [];
126
+ const { key: _key, labels, components, ...scalarArgs } = args;
127
+ const body = { ...scalarArgs };
128
+ await Promise.all(
129
+ Object.entries(SIMPLE_FIELD_CONFIG).map(
130
+ ([inputField, resolverKey]) => fieldResolver.getResolver(resolverKey).resolve(inputField, resolverKey, body, context, warnings)
131
+ )
132
+ );
133
+ for (const [inputField, resolverKey] of Object.entries(
134
+ ADD_DELETE_FIELD_CONFIG
135
+ )) {
136
+ const field = args[inputField];
137
+ if (!field) continue;
138
+ const resolvedField = await resolveAddDelete(
139
+ fieldResolver.getResolver(resolverKey),
140
+ inputField,
141
+ resolverKey,
142
+ field,
143
+ context,
144
+ warnings
145
+ );
146
+ if (Object.keys(resolvedField).length > 0)
147
+ body[inputField] = resolvedField;
148
+ }
149
+ await this.client.getApiClient().put(ENDPOINTS.UPDATE_TEST_CYCLE(args.key), body);
150
+ return {
151
+ structuredContent: UpdateTestCycleResponse.parse({
152
+ key: args.key,
153
+ updated: true
154
+ }),
155
+ content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
156
+ };
157
+ };
158
+ }
159
+ export {
160
+ UpdateTestCycle
161
+ };
@@ -98,7 +98,7 @@ const CreatePortalArgsSchema = z.object({
98
98
  "Whether authentication credentials are enabled for accessing the portal. When true, users can authenticate to access private content. Defaults to true"
99
99
  ),
100
100
  swaggerHubOrganizationId: z.string().describe(
101
- "The corresponding SwaggerHub organization UUID - required for portal creation. This links the portal to your SwaggerHub organization"
101
+ "The corresponding Swagger organization UUID - required for portal creation. This links the portal to your Swagger organization. Only one Portal per Swagger organization is allowed."
102
102
  ),
103
103
  openapiRenderer: z.string().optional().describe(
104
104
  "OpenAPI renderer type: 'SWAGGER_UI' (Swagger UI), 'ELEMENTS' (Stoplight Elements), or 'TOGGLE' (allows switching between both with Elements as default). Defaults to 'TOGGLE'"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartbear/mcp",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "MCP server for interacting SmartBear Products",
5
5
  "keywords": [
6
6
  "smartbear",