@browserstack/mcp-server 1.0.11 → 1.0.12

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/config.js CHANGED
@@ -8,11 +8,13 @@ if (!process.env.BROWSERSTACK_ACCESS_KEY ||
8
8
  class Config {
9
9
  browserstackUsername;
10
10
  browserstackAccessKey;
11
- constructor(browserstackUsername, browserstackAccessKey) {
11
+ DEV_MODE;
12
+ constructor(browserstackUsername, browserstackAccessKey, DEV_MODE) {
12
13
  this.browserstackUsername = browserstackUsername;
13
14
  this.browserstackAccessKey = browserstackAccessKey;
15
+ this.DEV_MODE = DEV_MODE;
14
16
  }
15
17
  }
16
18
  exports.Config = Config;
17
- const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY);
19
+ const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY, process.env.DEV_MODE === "true");
18
20
  exports.default = config;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.trackMCP = trackMCP;
7
+ const logger_1 = __importDefault(require("../logger"));
8
+ const config_1 = __importDefault(require("../config"));
9
+ const package_json_1 = __importDefault(require("../../package.json"));
10
+ const axios_1 = __importDefault(require("axios"));
11
+ function trackMCP(toolName, clientInfo, error) {
12
+ if (config_1.default.DEV_MODE) {
13
+ logger_1.default.info("Tracking MCP is disabled in dev mode");
14
+ return;
15
+ }
16
+ const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
17
+ const isSuccess = !error;
18
+ const mcpClient = clientInfo?.name || "unknown";
19
+ // Log client information
20
+ if (clientInfo?.name) {
21
+ logger_1.default.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
22
+ }
23
+ else {
24
+ logger_1.default.info("Client connected: unknown client");
25
+ }
26
+ const event = {
27
+ event_type: "MCPInstrumentation",
28
+ event_properties: {
29
+ mcp_version: package_json_1.default.version,
30
+ tool_name: toolName,
31
+ mcp_client: mcpClient,
32
+ success: isSuccess,
33
+ },
34
+ };
35
+ // Add error details if applicable
36
+ if (error) {
37
+ event.event_properties.error_message =
38
+ error instanceof Error ? error.message : String(error);
39
+ event.event_properties.error_type =
40
+ error instanceof Error ? error.constructor.name : "Unknown";
41
+ }
42
+ axios_1.default
43
+ .post(instrumentationEndpoint, event, {
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Authorization: `Basic ${Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64")}`,
47
+ },
48
+ timeout: 2000,
49
+ })
50
+ .catch(() => { });
51
+ }
@@ -3,41 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = addAccessibilityTools;
4
4
  const zod_1 = require("zod");
5
5
  const accessibility_1 = require("./accessiblity-utils/accessibility");
6
+ const instrumentation_1 = require("../lib/instrumentation");
6
7
  async function runAccessibilityScan(name, pageURL) {
7
- try {
8
- const response = await (0, accessibility_1.startAccessibilityScan)(name, [pageURL]);
9
- const scanId = response.data?.id;
10
- const scanRunId = response.data?.scanRunId;
11
- if (!scanId || !scanRunId) {
12
- throw new Error("Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists");
13
- }
14
- return {
15
- content: [
16
- {
17
- type: "text",
18
- text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
19
- },
20
- ],
21
- };
22
- }
23
- catch (error) {
24
- return {
25
- content: [
26
- {
27
- type: "text",
28
- text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
29
- isError: true,
30
- },
31
- ],
32
- isError: true,
33
- };
8
+ const response = await (0, accessibility_1.startAccessibilityScan)(name, [pageURL]);
9
+ const scanId = response.data?.id;
10
+ const scanRunId = response.data?.scanRunId;
11
+ if (!scanId || !scanRunId) {
12
+ throw new Error("Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists");
34
13
  }
14
+ return {
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
19
+ },
20
+ ],
21
+ };
35
22
  }
36
23
  function addAccessibilityTools(server) {
37
24
  server.tool("startAccessibilityScan", "Use this tool to start an accessibility scan for a list of URLs on BrowserStack.", {
38
25
  name: zod_1.z.string().describe("Name of the accessibility scan"),
39
26
  pageURL: zod_1.z.string().describe("The URL to scan for accessibility issues"),
40
27
  }, async (args) => {
41
- return runAccessibilityScan(args.name, args.pageURL);
28
+ try {
29
+ (0, instrumentation_1.trackMCP)("startAccessibilityScan", server.server.getClientVersion());
30
+ return await runAccessibilityScan(args.name, args.pageURL);
31
+ }
32
+ catch (error) {
33
+ (0, instrumentation_1.trackMCP)("startAccessibilityScan", server.server.getClientVersion(), error);
34
+ return {
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
39
+ isError: true,
40
+ },
41
+ ],
42
+ isError: true,
43
+ };
44
+ }
42
45
  });
43
46
  }
@@ -9,6 +9,7 @@ const zod_1 = require("zod");
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const start_session_1 = require("./applive-utils/start-session");
11
11
  const logger_1 = __importDefault(require("../logger"));
12
+ const instrumentation_1 = require("../lib/instrumentation");
12
13
  /**
13
14
  * Launches an App Live Session on BrowserStack.
14
15
  */
@@ -70,14 +71,17 @@ function addAppLiveTools(server) {
70
71
  .describe("The path to the .ipa or .apk file to install on the device. Always ask the user for the app path, do not assume it."),
71
72
  }, async (args) => {
72
73
  try {
73
- return startAppLiveSession(args);
74
+ (0, instrumentation_1.trackMCP)("runAppLiveSession", server.server.getClientVersion());
75
+ return await startAppLiveSession(args);
74
76
  }
75
77
  catch (error) {
78
+ logger_1.default.error("App live session failed: %s", error);
79
+ (0, instrumentation_1.trackMCP)("runAppLiveSession", server.server.getClientVersion(), error);
76
80
  return {
77
81
  content: [
78
82
  {
79
83
  type: "text",
80
- text: `Failed to start an app live session. Error: ${error}. Please open an issue on GitHub if the problem persists`,
84
+ text: `Failed to start app live session: ${error instanceof Error ? error.message : String(error)}`,
81
85
  isError: true,
82
86
  },
83
87
  ],
@@ -8,6 +8,7 @@ exports.default = addAutomateTools;
8
8
  const zod_1 = require("zod");
9
9
  const logger_1 = __importDefault(require("../logger"));
10
10
  const api_1 = require("../lib/api");
11
+ const instrumentation_1 = require("../lib/instrumentation");
11
12
  /**
12
13
  * Fetches failed network requests from a BrowserStack Automate session.
13
14
  * Returns network requests that resulted in errors or failed to complete.
@@ -31,22 +32,32 @@ async function getNetworkFailures(args) {
31
32
  };
32
33
  }
33
34
  catch (error) {
34
- const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
35
- logger_1.default.error("Failed to fetch network logs: %s", errorMessage);
36
- return {
37
- content: [
38
- {
39
- type: "text",
40
- text: `Failed to fetch network logs: ${errorMessage}`,
41
- isError: true,
42
- },
43
- ],
44
- isError: true,
45
- };
35
+ logger_1.default.error("Failed to fetch network logs: %s", error);
36
+ throw new Error(error instanceof Error ? error.message : String(error));
46
37
  }
47
38
  }
48
39
  function addAutomateTools(server) {
49
40
  server.tool("getNetworkFailures", "Use this tool to fetch failed network requests from a BrowserStack Automate session.", {
50
41
  sessionId: zod_1.z.string().describe("The Automate session ID."),
51
- }, getNetworkFailures);
42
+ }, async (args) => {
43
+ try {
44
+ (0, instrumentation_1.trackMCP)("getNetworkFailures", server.server.getClientVersion());
45
+ return await getNetworkFailures(args);
46
+ }
47
+ catch (error) {
48
+ const errorMessage = error instanceof Error ? error.message : String(error);
49
+ logger_1.default.error("Failed to fetch network logs: %s", errorMessage);
50
+ (0, instrumentation_1.trackMCP)("getNetworkFailures", server.server.getClientVersion(), error);
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text",
55
+ text: `Failed to fetch network logs: ${errorMessage}`,
56
+ isError: true,
57
+ },
58
+ ],
59
+ isError: true,
60
+ };
61
+ }
62
+ });
52
63
  }
@@ -4,6 +4,7 @@ exports.bootstrapProjectWithSDK = bootstrapProjectWithSDK;
4
4
  exports.default = addSDKTools;
5
5
  const zod_1 = require("zod");
6
6
  const instructions_1 = require("./sdk-utils/instructions");
7
+ const instrumentation_1 = require("../lib/instrumentation");
7
8
  /**
8
9
  * BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
9
10
  * This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
@@ -36,19 +37,17 @@ function addSDKTools(server) {
36
37
  .array(zod_1.z.enum(["windows", "macos", "android", "ios"]))
37
38
  .describe("The platforms the user wants to test on. Always ask this to the user, do not try to infer this."),
38
39
  }, async (args) => {
39
- const detectedBrowserAutomationFramework = args.detectedBrowserAutomationFramework;
40
- const detectedTestingFramework = args.detectedTestingFramework;
41
- const detectedLanguage = args.detectedLanguage;
42
- const desiredPlatforms = args.desiredPlatforms;
43
40
  try {
44
- return bootstrapProjectWithSDK({
45
- detectedBrowserAutomationFramework,
46
- detectedTestingFramework,
47
- detectedLanguage,
48
- desiredPlatforms,
41
+ (0, instrumentation_1.trackMCP)("runTestsOnBrowserStack", server.server.getClientVersion());
42
+ return await bootstrapProjectWithSDK({
43
+ detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
44
+ detectedTestingFramework: args.detectedTestingFramework,
45
+ detectedLanguage: args.detectedLanguage,
46
+ desiredPlatforms: args.desiredPlatforms,
49
47
  });
50
48
  }
51
49
  catch (error) {
50
+ (0, instrumentation_1.trackMCP)("runTestsOnBrowserStack", server.server.getClientVersion(), error);
52
51
  return {
53
52
  content: [
54
53
  {
@@ -8,6 +8,7 @@ const zod_1 = require("zod");
8
8
  const logger_1 = __importDefault(require("../logger"));
9
9
  const start_session_1 = require("./live-utils/start-session");
10
10
  const types_1 = require("./live-utils/types");
11
+ const instrumentation_1 = require("../lib/instrumentation");
11
12
  // Define the schema shape
12
13
  const LiveArgsShape = {
13
14
  platformType: zod_1.z
@@ -68,41 +69,28 @@ async function launchMobileSession(args) {
68
69
  async function runBrowserSession(rawArgs) {
69
70
  // Validate and narrow
70
71
  const args = LiveArgsSchema.parse(rawArgs);
71
- try {
72
- // Branch desktop vs mobile and delegate
73
- const launchUrl = args.platformType === types_1.PlatformType.DESKTOP
74
- ? await launchDesktopSession(args)
75
- : await launchMobileSession(args);
76
- return {
77
- content: [
78
- {
79
- type: "text",
80
- text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
81
- },
82
- ],
83
- };
84
- }
85
- catch (err) {
86
- logger_1.default.error("Live session failed: %s", err);
87
- return {
88
- content: [
89
- {
90
- type: "text",
91
- text: `❌ Failed to start session: ${err.message || err}`,
92
- isError: true,
93
- },
94
- ],
95
- isError: true,
96
- };
97
- }
72
+ // Branch desktop vs mobile and delegate
73
+ const launchUrl = args.platformType === types_1.PlatformType.DESKTOP
74
+ ? await launchDesktopSession(args)
75
+ : await launchMobileSession(args);
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
81
+ },
82
+ ],
83
+ };
98
84
  }
99
85
  function addBrowserLiveTools(server) {
100
86
  server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
101
87
  try {
102
- const result = await runBrowserSession(args);
103
- return result;
88
+ (0, instrumentation_1.trackMCP)("runBrowserLiveSession", server.server.getClientVersion());
89
+ return await runBrowserSession(args);
104
90
  }
105
91
  catch (error) {
92
+ logger_1.default.error("Live session failed: %s", error);
93
+ (0, instrumentation_1.trackMCP)("runBrowserLiveSession", server.server.getClientVersion(), error);
106
94
  return {
107
95
  content: [
108
96
  {
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.getFailuresInLastRun = getFailuresInLastRun;
4
7
  exports.default = addObservabilityTools;
5
8
  const zod_1 = require("zod");
6
9
  const api_1 = require("../lib/api");
10
+ const instrumentation_1 = require("../lib/instrumentation");
11
+ const logger_1 = __importDefault(require("../logger"));
7
12
  async function getFailuresInLastRun(buildName, projectName) {
8
13
  const buildsData = await (0, api_1.getLatestO11YBuildInfo)(buildName, projectName);
9
14
  const observabilityUrl = buildsData.observability_url;
@@ -40,9 +45,12 @@ function addObservabilityTools(server) {
40
45
  .describe("Name of the project to get failures for. This is the 'projectName' key in the browserstack.yml file. If not sure, ask the user for the project name."),
41
46
  }, async (args) => {
42
47
  try {
43
- return getFailuresInLastRun(args.buildName, args.projectName);
48
+ (0, instrumentation_1.trackMCP)("getFailuresInLastRun", server.server.getClientVersion());
49
+ return await getFailuresInLastRun(args.buildName, args.projectName);
44
50
  }
45
51
  catch (error) {
52
+ logger_1.default.error("Failed to get failures in the last run: %s", error);
53
+ (0, instrumentation_1.trackMCP)("getFailuresInLastRun", server.server.getClientVersion(), error);
46
54
  return {
47
55
  content: [
48
56
  {
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CreateTestRunSchema = void 0;
7
+ exports.createTestRun = createTestRun;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const config_1 = __importDefault(require("../../config"));
10
+ const zod_1 = require("zod");
11
+ const error_1 = require("../../lib/error");
12
+ /**
13
+ * Schema for creating a test run.
14
+ */
15
+ exports.CreateTestRunSchema = zod_1.z.object({
16
+ project_identifier: zod_1.z
17
+ .string()
18
+ .describe("Identifier of the project in which to create the test run."),
19
+ test_run: zod_1.z.object({
20
+ name: zod_1.z.string().describe("Name of the test run"),
21
+ description: zod_1.z
22
+ .string()
23
+ .optional()
24
+ .describe("Brief information about the test run"),
25
+ run_state: zod_1.z
26
+ .enum([
27
+ "new_run",
28
+ "in_progress",
29
+ "under_review",
30
+ "rejected",
31
+ "done",
32
+ "closed",
33
+ ])
34
+ .optional()
35
+ .describe("State of the test run. One of new_run, in_progress, under_review, rejected, done, closed"),
36
+ issues: zod_1.z.array(zod_1.z.string()).optional().describe("Linked issue IDs"),
37
+ issue_tracker: zod_1.z
38
+ .object({ name: zod_1.z.string(), host: zod_1.z.string().url() })
39
+ .optional()
40
+ .describe("Issue tracker configuration"),
41
+ test_cases: zod_1.z
42
+ .array(zod_1.z.string())
43
+ .optional()
44
+ .describe("List of test case IDs"),
45
+ folder_ids: zod_1.z
46
+ .array(zod_1.z.number())
47
+ .optional()
48
+ .describe("Folder IDs to include"),
49
+ }),
50
+ });
51
+ /**
52
+ * Creates a test run via BrowserStack Test Management API.
53
+ */
54
+ async function createTestRun(rawArgs) {
55
+ try {
56
+ const inputArgs = {
57
+ ...rawArgs,
58
+ test_run: {
59
+ ...rawArgs.test_run,
60
+ include_all: false,
61
+ },
62
+ };
63
+ const args = exports.CreateTestRunSchema.parse(inputArgs);
64
+ const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs`;
65
+ const response = await axios_1.default.post(url, { test_run: args.test_run }, {
66
+ auth: {
67
+ username: config_1.default.browserstackUsername,
68
+ password: config_1.default.browserstackAccessKey,
69
+ },
70
+ headers: { "Content-Type": "application/json" },
71
+ });
72
+ const data = response.data;
73
+ if (!data.success) {
74
+ throw new Error(`API returned unsuccessful response: ${JSON.stringify(data)}`);
75
+ }
76
+ // Assume data.test_run contains created run info
77
+ const created = data.test_run || data;
78
+ const runId = created.identifier || created.id || created.name;
79
+ const summary = `Successfully created test run ${runId}`;
80
+ return {
81
+ content: [
82
+ { type: "text", text: summary },
83
+ { type: "text", text: JSON.stringify(created, null, 2) },
84
+ ],
85
+ };
86
+ }
87
+ catch (err) {
88
+ return (0, error_1.formatAxiosError)(err, "Failed to create test run");
89
+ }
90
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ListTestCasesSchema = void 0;
7
+ exports.listTestCases = listTestCases;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const config_1 = __importDefault(require("../../config"));
10
+ const zod_1 = require("zod");
11
+ const error_1 = require("../../lib/error");
12
+ /**
13
+ * Schema for listing test cases with optional filters.
14
+ */
15
+ exports.ListTestCasesSchema = zod_1.z.object({
16
+ project_identifier: zod_1.z
17
+ .string()
18
+ .describe("Identifier of the project to fetch test cases from. Example: PR-12345"),
19
+ folder_id: zod_1.z
20
+ .string()
21
+ .optional()
22
+ .describe("If provided, only return cases in this folder."),
23
+ case_type: zod_1.z
24
+ .string()
25
+ .optional()
26
+ .describe("Comma-separated list of case types (e.g. functional,regression)."),
27
+ priority: zod_1.z
28
+ .string()
29
+ .optional()
30
+ .describe("Comma-separated list of priorities (e.g. critical,medium,low)."),
31
+ p: zod_1.z.number().optional().describe("Page number."),
32
+ });
33
+ /**
34
+ * Calls BrowserStack Test Management to list test cases with filters.
35
+ */
36
+ async function listTestCases(args) {
37
+ try {
38
+ // Build query string
39
+ const params = new URLSearchParams();
40
+ if (args.folder_id)
41
+ params.append("folder_id", args.folder_id);
42
+ if (args.case_type)
43
+ params.append("case_type", args.case_type);
44
+ if (args.priority)
45
+ params.append("priority", args.priority);
46
+ if (args.p !== undefined)
47
+ params.append("p", args.p.toString());
48
+ const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-cases?${params.toString()}`;
49
+ const resp = await axios_1.default.get(url, {
50
+ auth: {
51
+ username: config_1.default.browserstackUsername,
52
+ password: config_1.default.browserstackAccessKey,
53
+ },
54
+ });
55
+ const resp_data = resp.data;
56
+ if (!resp_data.success) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: `Failed to list test cases: ${JSON.stringify(resp_data)}`,
62
+ isError: true,
63
+ },
64
+ ],
65
+ isError: true,
66
+ };
67
+ }
68
+ const { test_cases, info } = resp_data;
69
+ const count = info?.count ?? test_cases.length;
70
+ // Summary for more focused output
71
+ const summary = test_cases
72
+ .map((tc) => `• ${tc.identifier}: ${tc.title} [${tc.case_type} | ${tc.priority}]`)
73
+ .join("\n");
74
+ return {
75
+ content: [
76
+ {
77
+ type: "text",
78
+ text: `Found ${count} test case(s):\n\n${summary}`,
79
+ },
80
+ {
81
+ type: "text",
82
+ text: JSON.stringify(test_cases, null, 2),
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ catch (err) {
88
+ return (0, error_1.formatAxiosError)(err, "Failed to list test cases");
89
+ }
90
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ListTestRunsSchema = void 0;
7
+ exports.listTestRuns = listTestRuns;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const config_1 = __importDefault(require("../../config"));
10
+ const zod_1 = require("zod");
11
+ const error_1 = require("../../lib/error");
12
+ /**
13
+ * Schema for listing test runs with optional filters.
14
+ */
15
+ exports.ListTestRunsSchema = zod_1.z.object({
16
+ project_identifier: zod_1.z
17
+ .string()
18
+ .describe("Identifier of the project to fetch test runs from (e.g., PR-12345)"),
19
+ run_state: zod_1.z
20
+ .string()
21
+ .optional()
22
+ .describe("Return all test runs with this state (comma-separated)"),
23
+ });
24
+ /**
25
+ * Fetches and formats the list of test runs for a project.
26
+ */
27
+ async function listTestRuns(args) {
28
+ try {
29
+ const params = new URLSearchParams();
30
+ if (args.run_state) {
31
+ params.set("run_state", args.run_state);
32
+ }
33
+ const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs?` + params.toString();
34
+ const resp = await axios_1.default.get(url, {
35
+ auth: {
36
+ username: config_1.default.browserstackUsername,
37
+ password: config_1.default.browserstackAccessKey,
38
+ },
39
+ });
40
+ const data = resp.data;
41
+ if (!data.success) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: `Failed to list test runs: ${JSON.stringify(data)}`,
47
+ isError: true,
48
+ },
49
+ ],
50
+ isError: true,
51
+ };
52
+ }
53
+ const runs = data.test_runs;
54
+ const count = runs.length;
55
+ const summary = runs
56
+ .map((tr) => `• ${tr.identifier}: ${tr.name} [${tr.run_state}]`)
57
+ .join("\n");
58
+ return {
59
+ content: [
60
+ { type: "text", text: `Found ${count} test run(s):\n\n${summary}` },
61
+ { type: "text", text: JSON.stringify(runs, null, 2) },
62
+ ],
63
+ };
64
+ }
65
+ catch (err) {
66
+ return (0, error_1.formatAxiosError)(err, "Failed to list test runs");
67
+ }
68
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UpdateTestRunSchema = void 0;
7
+ exports.updateTestRun = updateTestRun;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const config_1 = __importDefault(require("../../config"));
10
+ const zod_1 = require("zod");
11
+ const error_1 = require("../../lib/error");
12
+ /**
13
+ * Schema for updating a test run with partial fields.
14
+ */
15
+ exports.UpdateTestRunSchema = zod_1.z.object({
16
+ project_identifier: zod_1.z
17
+ .string()
18
+ .describe("Project identifier (e.g., PR-12345)"),
19
+ test_run_id: zod_1.z.string().describe("Test run identifier (e.g., TR-678)"),
20
+ test_run: zod_1.z.object({
21
+ name: zod_1.z.string().optional().describe("New name of the test run"),
22
+ run_state: zod_1.z
23
+ .enum([
24
+ "new_run",
25
+ "in_progress",
26
+ "under_review",
27
+ "rejected",
28
+ "done",
29
+ "closed",
30
+ ])
31
+ .optional()
32
+ .describe("Updated state of the test run"),
33
+ }),
34
+ });
35
+ /**
36
+ * Partially updates an existing test run.
37
+ */
38
+ async function updateTestRun(args) {
39
+ try {
40
+ const body = { test_run: args.test_run };
41
+ const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
42
+ const resp = await axios_1.default.patch(url, body, {
43
+ auth: {
44
+ username: config_1.default.browserstackUsername,
45
+ password: config_1.default.browserstackAccessKey,
46
+ },
47
+ });
48
+ const data = resp.data;
49
+ if (!data.success) {
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: `Failed to update test run: ${JSON.stringify(data)}`,
55
+ isError: true,
56
+ },
57
+ ],
58
+ isError: true,
59
+ };
60
+ }
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text",
65
+ text: `Successfully updated test run ${args.test_run_id}`,
66
+ },
67
+ { type: "text", text: JSON.stringify(data.testrun || data, null, 2) },
68
+ ],
69
+ };
70
+ }
71
+ catch (err) {
72
+ return (0, error_1.formatAxiosError)(err, "Failed to update test run");
73
+ }
74
+ }
@@ -1,18 +1,36 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createProjectOrFolderTool = createProjectOrFolderTool;
4
7
  exports.createTestCaseTool = createTestCaseTool;
8
+ exports.listTestCasesTool = listTestCasesTool;
9
+ exports.createTestRunTool = createTestRunTool;
10
+ exports.listTestRunsTool = listTestRunsTool;
11
+ exports.updateTestRunTool = updateTestRunTool;
5
12
  exports.default = addTestManagementTools;
13
+ const instrumentation_1 = require("../lib/instrumentation");
14
+ const logger_1 = __importDefault(require("../logger"));
6
15
  const create_project_folder_1 = require("./testmanagement-utils/create-project-folder");
7
16
  const create_testcase_1 = require("./testmanagement-utils/create-testcase");
17
+ let serverInstance;
18
+ const list_testcases_1 = require("./testmanagement-utils/list-testcases");
19
+ const create_testrun_1 = require("./testmanagement-utils/create-testrun");
20
+ const list_testruns_1 = require("./testmanagement-utils/list-testruns");
21
+ const update_testrun_1 = require("./testmanagement-utils/update-testrun");
22
+ //TODO: Moving the traceMCP and catch block to the parent(server) function
8
23
  /**
9
24
  * Wrapper to call createProjectOrFolder util.
10
25
  */
11
26
  async function createProjectOrFolderTool(args) {
12
27
  try {
28
+ (0, instrumentation_1.trackMCP)("createProjectOrFolder", serverInstance.server.getClientVersion());
13
29
  return await (0, create_project_folder_1.createProjectOrFolder)(args);
14
30
  }
15
31
  catch (err) {
32
+ logger_1.default.error("Failed to create project/folder: %s", err);
33
+ (0, instrumentation_1.trackMCP)("createProjectOrFolder", serverInstance.server.getClientVersion(), err);
16
34
  return {
17
35
  content: [
18
36
  {
@@ -32,9 +50,12 @@ async function createTestCaseTool(args) {
32
50
  // Sanitize input arguments
33
51
  const cleanedArgs = (0, create_testcase_1.sanitizeArgs)(args);
34
52
  try {
53
+ (0, instrumentation_1.trackMCP)("createTestCase", serverInstance.server.getClientVersion());
35
54
  return await (0, create_testcase_1.createTestCase)(cleanedArgs);
36
55
  }
37
56
  catch (err) {
57
+ logger_1.default.error("Failed to create test case: %s", err);
58
+ (0, instrumentation_1.trackMCP)("createTestCase", serverInstance.server.getClientVersion(), err);
38
59
  return {
39
60
  content: [
40
61
  {
@@ -47,10 +68,97 @@ async function createTestCaseTool(args) {
47
68
  };
48
69
  }
49
70
  }
71
+ /**
72
+ * Lists test cases in a project with optional filters (status, priority, custom fields, etc.)
73
+ */
74
+ async function listTestCasesTool(args) {
75
+ try {
76
+ return await (0, list_testcases_1.listTestCases)(args);
77
+ }
78
+ catch (err) {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Failed to list test cases: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
84
+ isError: true,
85
+ },
86
+ ],
87
+ isError: true,
88
+ };
89
+ }
90
+ }
91
+ /**
92
+ * Creates a test run in BrowserStack Test Management.
93
+ */
94
+ async function createTestRunTool(args) {
95
+ try {
96
+ return await (0, create_testrun_1.createTestRun)(args);
97
+ }
98
+ catch (err) {
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: `Failed to create test run: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
104
+ isError: true,
105
+ },
106
+ ],
107
+ isError: true,
108
+ };
109
+ }
110
+ }
111
+ /**
112
+ * Lists test runs in a project with optional filters (date ranges, assignee, state, etc.)
113
+ */
114
+ async function listTestRunsTool(args) {
115
+ try {
116
+ return await (0, list_testruns_1.listTestRuns)(args);
117
+ }
118
+ catch (err) {
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: `Failed to list test runs: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
124
+ isError: true,
125
+ },
126
+ ],
127
+ isError: true,
128
+ };
129
+ }
130
+ }
131
+ /**
132
+ * Updates a test run in BrowserStack Test Management.
133
+ * This function allows for partial updates to an existing test run.
134
+ * It takes the project identifier and test run ID as parameters.
135
+ */
136
+ async function updateTestRunTool(args) {
137
+ try {
138
+ return await (0, update_testrun_1.updateTestRun)(args);
139
+ }
140
+ catch (err) {
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: `Failed to update test run: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
146
+ isError: true,
147
+ },
148
+ ],
149
+ isError: true,
150
+ };
151
+ }
152
+ }
50
153
  /**
51
154
  * Registers both project/folder and test-case tools.
52
155
  */
53
156
  function addTestManagementTools(server) {
157
+ serverInstance = server;
54
158
  server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management.", create_project_folder_1.CreateProjFoldSchema.shape, createProjectOrFolderTool);
55
159
  server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management.", create_testcase_1.CreateTestCaseSchema.shape, createTestCaseTool);
160
+ server.tool("listTestCases", "List test cases in a project with optional filters (status, priority, custom fields, etc.)", list_testcases_1.ListTestCasesSchema.shape, listTestCasesTool);
161
+ server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", create_testrun_1.CreateTestRunSchema.shape, createTestRunTool);
162
+ server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)", list_testruns_1.ListTestRunsSchema.shape, listTestRunsTool);
163
+ server.tool("updateTestRun", "Update a test run in BrowserStack Test Management.", update_testrun_1.UpdateTestRunSchema.shape, updateTestRunTool);
56
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "repository": {