@browserstack/mcp-server 1.2.25 → 1.2.27-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -334,7 +334,7 @@ As of now we support 20 tools.
334
334
  List all test runs from the 'Shopping App' project that were executed last week and are currently marked in-progress
335
335
  ```
336
336
 
337
- 6. `updateTestRun` — Partially update a test run (status, tags, notes, associated test cases).
337
+ 6. `updateTestRun` — Update a test run's name/state and/or add test cases to it.
338
338
  **Prompt example**
339
339
 
340
340
  ```text
@@ -65,7 +65,13 @@ export async function listTestCases(args, config) {
65
65
  const count = info?.count ?? test_cases.length;
66
66
  // Summary for more focused output
67
67
  const summary = test_cases
68
- .map((tc) => `• ${tc.identifier}: ${tc.title} [${tc.case_type} | ${tc.priority}]`)
68
+ .map((tc) => {
69
+ const links = (tc.issues ?? [])
70
+ .filter((i) => i?.issue_type && i?.jira_id)
71
+ .map((i) => `${i.issue_type}:${i.jira_id}`)
72
+ .join(", ");
73
+ return `• ${tc.identifier}: ${tc.title} [${tc.case_type} | ${tc.priority}]${links ? ` {linked: ${links}}` : ""}`;
74
+ })
69
75
  .join("\n");
70
76
  return {
71
77
  content: [
@@ -17,11 +17,26 @@ export declare const UpdateTestRunSchema: z.ZodObject<{
17
17
  rejected: "rejected";
18
18
  closed: "closed";
19
19
  }>>;
20
+ add_test_cases: z.ZodOptional<z.ZodArray<z.ZodObject<{
21
+ test_case_ids: z.ZodArray<z.ZodString>;
22
+ configuration_ids: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
23
+ }, z.core.$strip>>>;
24
+ preserve_existing_results: z.ZodOptional<z.ZodBoolean>;
20
25
  }, z.core.$strip>;
21
26
  }, z.core.$strip>;
22
27
  type UpdateTestRunArgs = z.infer<typeof UpdateTestRunSchema>;
23
28
  /**
24
29
  * Partially updates an existing test run.
30
+ *
31
+ * Dispatches to one of two BrowserStack endpoints based on the fields provided,
32
+ * mirroring how the platform splits these concerns across two endpoints:
33
+ * - metadata (name / run_state) -> PATCH .../test-runs/{id}/update
34
+ * - adding test cases -> PATCH .../test-runs/{id}/test-cases
35
+ *
36
+ * Either or both may be supplied in one call; each provided concern hits its
37
+ * own endpoint and both outcomes are reported. At least one must be provided.
38
+ * Removing test cases is intentionally not exposed — this tool is
39
+ * non-destructive.
25
40
  */
26
41
  export declare function updateTestRun(args: UpdateTestRunArgs, config: BrowserStackConfig): Promise<CallToolResult>;
27
42
  export {};
@@ -3,6 +3,19 @@ import { getBrowserStackAuth } from "../../lib/get-auth.js";
3
3
  import { z } from "zod";
4
4
  import { formatAxiosError } from "../../lib/error.js";
5
5
  import { getTMBaseURL } from "../../lib/tm-base-url.js";
6
+ /**
7
+ * Selection of test cases (with optional configurations) to add.
8
+ */
9
+ const TestCaseSelectionSchema = z.object({
10
+ test_case_ids: z
11
+ .array(z.string())
12
+ .min(1)
13
+ .describe("Test case IDs, e.g. TC-123"),
14
+ configuration_ids: z
15
+ .array(z.number())
16
+ .optional()
17
+ .describe("Configuration IDs to apply"),
18
+ });
6
19
  /**
7
20
  * Schema for updating a test run with partial fields.
8
21
  */
@@ -24,22 +37,82 @@ export const UpdateTestRunSchema = z.object({
24
37
  ])
25
38
  .optional()
26
39
  .describe("Updated state of the test run"),
40
+ add_test_cases: z
41
+ .array(TestCaseSelectionSchema)
42
+ .optional()
43
+ .describe("Test cases to add to the run"),
44
+ preserve_existing_results: z
45
+ .boolean()
46
+ .optional()
47
+ .describe("Keep existing results when adding cases (default true)"),
27
48
  }),
28
49
  });
50
+ /**
51
+ * Builds the HTTP Basic auth header from per-request config credentials.
52
+ */
53
+ function buildAuthHeader(config) {
54
+ return "Basic " + Buffer.from(getBrowserStackAuth(config)).toString("base64");
55
+ }
29
56
  /**
30
57
  * Partially updates an existing test run.
58
+ *
59
+ * Dispatches to one of two BrowserStack endpoints based on the fields provided,
60
+ * mirroring how the platform splits these concerns across two endpoints:
61
+ * - metadata (name / run_state) -> PATCH .../test-runs/{id}/update
62
+ * - adding test cases -> PATCH .../test-runs/{id}/test-cases
63
+ *
64
+ * Either or both may be supplied in one call; each provided concern hits its
65
+ * own endpoint and both outcomes are reported. At least one must be provided.
66
+ * Removing test cases is intentionally not exposed — this tool is
67
+ * non-destructive.
31
68
  */
32
69
  export async function updateTestRun(args, config) {
70
+ const { name, run_state, add_test_cases } = args.test_run;
71
+ const addIds = add_test_cases?.flatMap((s) => s.test_case_ids) ?? [];
72
+ const hasTestCases = addIds.length > 0;
73
+ const hasMetadata = name !== undefined || run_state !== undefined;
74
+ if (!hasTestCases && !hasMetadata) {
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: "Nothing to update: provide name/run_state and/or add_test_cases.",
80
+ },
81
+ ],
82
+ isError: true,
83
+ };
84
+ }
85
+ const tmBaseUrl = await getTMBaseURL(config);
86
+ const authHeader = buildAuthHeader(config);
87
+ const tasks = [];
88
+ if (hasMetadata) {
89
+ tasks.push(updateTestRunMetadata(args, tmBaseUrl, authHeader));
90
+ }
91
+ if (hasTestCases) {
92
+ tasks.push(updateTestRunTestCases(args, tmBaseUrl, authHeader));
93
+ }
94
+ const results = await Promise.all(tasks);
95
+ if (results.length === 1) {
96
+ return results[0];
97
+ }
98
+ // Both concerns updated: aggregate outcomes; surface an error if either failed.
99
+ return {
100
+ content: results.flatMap((r) => r.content),
101
+ isError: results.some((r) => r.isError),
102
+ };
103
+ }
104
+ /**
105
+ * Updates test run metadata (name / run_state) via the /update endpoint.
106
+ */
107
+ async function updateTestRunMetadata(args, baseUrl, authHeader) {
33
108
  try {
34
- const body = { test_run: args.test_run };
35
- const tmBaseUrl = await getTMBaseURL(config);
36
- const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
37
- const authString = getBrowserStackAuth(config);
38
- const [username, password] = authString.split(":");
109
+ const { name, run_state } = args.test_run;
110
+ const body = { test_run: { name, run_state } };
111
+ const url = `${baseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
39
112
  const resp = await apiClient.patch({
40
113
  url,
41
114
  headers: {
42
- Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
115
+ Authorization: authHeader,
43
116
  "Content-Type": "application/json",
44
117
  },
45
118
  body,
@@ -70,3 +143,52 @@ export async function updateTestRun(args, config) {
70
143
  return formatAxiosError(err, "Failed to update test run");
71
144
  }
72
145
  }
146
+ /**
147
+ * Adds test cases to a run via the /test-cases endpoint.
148
+ * This call is applied asynchronously by the backend.
149
+ */
150
+ async function updateTestRunTestCases(args, baseUrl, authHeader) {
151
+ try {
152
+ const { add_test_cases, preserve_existing_results } = args.test_run;
153
+ const body = {
154
+ test_run: {
155
+ add_test_cases,
156
+ preserve_existing_results: preserve_existing_results ?? true,
157
+ },
158
+ };
159
+ const url = `${baseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/test-cases`;
160
+ const resp = await apiClient.patch({
161
+ url,
162
+ headers: {
163
+ Authorization: authHeader,
164
+ "Content-Type": "application/json",
165
+ },
166
+ body,
167
+ });
168
+ const data = resp.data;
169
+ if (!data.success) {
170
+ return {
171
+ content: [
172
+ {
173
+ type: "text",
174
+ text: `Failed to update test run test cases: ${JSON.stringify(data)}`,
175
+ },
176
+ ],
177
+ isError: true,
178
+ };
179
+ }
180
+ const added = add_test_cases?.flatMap((s) => s.test_case_ids) ?? [];
181
+ return {
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: `Queued test-case update for ${args.test_run_id} (added ${added.length}); changes apply asynchronously.`,
186
+ },
187
+ { type: "text", text: JSON.stringify(data, null, 2) },
188
+ ],
189
+ };
190
+ }
191
+ catch (err) {
192
+ return formatAxiosError(err, "Failed to update test run test cases");
193
+ }
194
+ }
@@ -400,7 +400,7 @@ export default function addTestManagementTools(server, config) {
400
400
  tools.listTestCaseTemplates = server.tool("listTestCaseTemplates", "List test-case templates with their numeric template_id. Use the id with createTestCase to apply a custom template (the 'template' slug only selects system templates).", ListTemplatesSchema.shape, (args) => listTemplatesTool(args, config, server));
401
401
  tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", CreateTestRunSchema.shape, (args) => createTestRunTool(args, config, server));
402
402
  tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)", ListTestRunsSchema.shape, (args) => listTestRunsTool(args, config, server));
403
- tools.updateTestRun = server.tool("updateTestRun", "Update a test run in BrowserStack Test Management.", UpdateTestRunSchema.shape, (args) => updateTestRunTool(args, config, server));
403
+ tools.updateTestRun = server.tool("updateTestRun", "Update a test run's metadata and/or add test cases to it.", UpdateTestRunSchema.shape, (args) => updateTestRunTool(args, config, server));
404
404
  tools.addTestResult = server.tool("addTestResult", "Add a test result to a specific test run via BrowserStack Test Management API.", AddTestResultSchema.shape, (args) => addTestResultTool(args, config, server));
405
405
  tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack.", UploadFileSchema.shape, (args) => uploadProductRequirementFileTool(args, config, server));
406
406
  tools.createTestCasesFromFile = server.tool("createTestCasesFromFile", "Generate test cases from a file in BrowserStack Test Management using the Test Case Generator AI Agent.", CreateTestCasesFromFileSchema.shape, (args, context) => createTestCasesFromFileTool(args, context, config, server));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.2.25",
3
+ "version": "1.2.27-beta.2",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",