@browserstack/mcp-server 1.0.9 → 1.0.11

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,52 @@
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.getDevicesAndBrowsers = getDevicesAndBrowsers;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".browserstack", "combined_cache");
11
+ const CACHE_FILE = path_1.default.join(CACHE_DIR, "data.json");
12
+ const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
13
+ const URLS = {
14
+ live: "https://www.browserstack.com/list-of-browsers-and-platforms/live.json",
15
+ app_live: "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
16
+ };
17
+ /**
18
+ * Fetches and caches both BrowserStack datasets (live + app_live) with a shared TTL.
19
+ */
20
+ async function getDevicesAndBrowsers(type) {
21
+ if (!fs_1.default.existsSync(CACHE_DIR)) {
22
+ fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
23
+ }
24
+ let cache = {};
25
+ if (fs_1.default.existsSync(CACHE_FILE)) {
26
+ const stats = fs_1.default.statSync(CACHE_FILE);
27
+ if (Date.now() - stats.mtimeMs < TTL_MS) {
28
+ try {
29
+ cache = JSON.parse(fs_1.default.readFileSync(CACHE_FILE, "utf8"));
30
+ return cache[type];
31
+ }
32
+ catch (error) {
33
+ console.error("Error parsing cache file:", error);
34
+ // Continue with fetching fresh data
35
+ }
36
+ }
37
+ }
38
+ const [liveRes, appLiveRes] = await Promise.all([
39
+ fetch(URLS.live),
40
+ fetch(URLS.app_live),
41
+ ]);
42
+ if (!liveRes.ok || !appLiveRes.ok) {
43
+ throw new Error(`Failed to fetch configuration from BrowserStack : live=${liveRes.statusText}, app_live=${appLiveRes.statusText}`);
44
+ }
45
+ const [liveData, appLiveData] = await Promise.all([
46
+ liveRes.json(),
47
+ appLiveRes.json(),
48
+ ]);
49
+ cache = { live: liveData, app_live: appLiveData };
50
+ fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
51
+ return cache[type];
52
+ }
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startSession = startSession;
7
7
  const child_process_1 = __importDefault(require("child_process"));
8
8
  const logger_1 = __importDefault(require("../../logger"));
9
- const device_cache_1 = require("./device-cache");
9
+ const device_cache_1 = require("../../lib/device-cache");
10
10
  const fuzzy_search_1 = require("./fuzzy-search");
11
11
  const utils_1 = require("../../lib/utils");
12
12
  const upload_app_1 = require("./upload-app");
@@ -19,7 +19,7 @@ const upload_app_1 = require("./upload-app");
19
19
  async function startSession(args) {
20
20
  const { appPath, desiredPlatform, desiredPhone } = args;
21
21
  let { desiredPlatformVersion } = args;
22
- const data = await (0, device_cache_1.getAppLiveData)();
22
+ const data = await (0, device_cache_1.getDevicesAndBrowsers)("app_live");
23
23
  const allDevices = data.mobile.flatMap((group) => group.devices.map((dev) => ({ ...dev, os: group.os })));
24
24
  desiredPlatformVersion = resolvePlatformVersion(allDevices, desiredPlatform, desiredPlatformVersion);
25
25
  const filteredDevices = filterDevicesByPlatformAndVersion(allDevices, desiredPlatform, desiredPlatformVersion);
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterDesktop = filterDesktop;
4
+ const device_cache_1 = require("../../lib/device-cache");
5
+ const version_resolver_1 = require("./version-resolver");
6
+ const fuzzy_1 = require("../../lib/fuzzy");
7
+ async function filterDesktop(args) {
8
+ const data = await (0, device_cache_1.getDevicesAndBrowsers)("live");
9
+ const allEntries = getAllDesktopEntries(data);
10
+ // Filter OS
11
+ const osList = filterByOS(allEntries, args.os);
12
+ // Filter browser
13
+ const browserList = filterByBrowser(osList, args.browser, args.os);
14
+ // Resolve OS version
15
+ const uniqueOSVersions = getUniqueOSVersions(browserList);
16
+ const chosenOS = resolveOSVersion(args.os, args.osVersion, uniqueOSVersions);
17
+ // Filter entries based on chosen OS version
18
+ const entriesForOS = filterByOSVersion(browserList, chosenOS);
19
+ // Resolve browser version
20
+ const browserVersions = entriesForOS.map((e) => e.browser_version);
21
+ const chosenBrowserVersion = (0, version_resolver_1.resolveVersion)(args.browserVersion, browserVersions);
22
+ // Find final entry
23
+ const finalEntry = entriesForOS.find((e) => e.browser_version === chosenBrowserVersion);
24
+ if (!finalEntry) {
25
+ throw new Error(`No entry for browser version "${args.browserVersion}".`);
26
+ }
27
+ // Add notes if versions were adjusted
28
+ addNotes(finalEntry, args, chosenOS, chosenBrowserVersion);
29
+ return finalEntry;
30
+ }
31
+ function getAllDesktopEntries(data) {
32
+ return data.desktop.flatMap((plat) => plat.browsers.map((b) => ({
33
+ os: plat.os,
34
+ os_version: plat.os_version,
35
+ browser: b.browser,
36
+ browser_version: b.browser_version,
37
+ })));
38
+ }
39
+ function filterByOS(entries, os) {
40
+ const filtered = entries.filter((e) => e.os === os);
41
+ if (!filtered.length)
42
+ throw new Error(`No OS entries for "${os}".`);
43
+ return filtered;
44
+ }
45
+ function filterByBrowser(entries, browser, os) {
46
+ const filtered = entries.filter((e) => e.browser === browser);
47
+ if (!filtered.length)
48
+ throw new Error(`No browser "${browser}" on ${os}.`);
49
+ return filtered;
50
+ }
51
+ function getUniqueOSVersions(entries) {
52
+ return Array.from(new Set(entries.map((e) => e.os_version)));
53
+ }
54
+ function resolveOSVersion(os, requestedVersion, availableVersions) {
55
+ if (os === "OS X") {
56
+ return resolveMacOSVersion(requestedVersion, availableVersions);
57
+ }
58
+ else {
59
+ // For Windows, use semantic versioning
60
+ return (0, version_resolver_1.resolveVersion)(requestedVersion, availableVersions);
61
+ }
62
+ }
63
+ function resolveMacOSVersion(requested, available) {
64
+ if (requested === "latest") {
65
+ return available[available.length - 1];
66
+ }
67
+ else if (requested === "oldest") {
68
+ return available[0];
69
+ }
70
+ else {
71
+ // Try fuzzy matching
72
+ const fuzzy = (0, fuzzy_1.customFuzzySearch)(available.map((v) => ({ os_version: v })), ["os_version"], requested, 1);
73
+ const matched = fuzzy.length ? fuzzy[0].os_version : requested;
74
+ // Fallback if not valid
75
+ return available.includes(matched) ? matched : available[0];
76
+ }
77
+ }
78
+ function filterByOSVersion(entries, osVersion) {
79
+ return entries.filter((e) => e.os_version === osVersion);
80
+ }
81
+ function addNotes(entry, args, resolvedOS, resolvedBrowser) {
82
+ if (args.osVersion !== resolvedOS &&
83
+ args.osVersion !== "latest" &&
84
+ args.osVersion !== "oldest") {
85
+ entry.notes = `Note: OS version ${args.osVersion} was not found. Using "${resolvedOS}" instead.`;
86
+ }
87
+ if (args.browserVersion !== resolvedBrowser &&
88
+ args.browserVersion !== "latest" &&
89
+ args.browserVersion !== "oldest") {
90
+ if (!entry.notes) {
91
+ entry.notes = `Note: `;
92
+ }
93
+ else {
94
+ entry.notes += ` `;
95
+ }
96
+ entry.notes += `Browser version ${args.browserVersion} was not found. Using "${resolvedBrowser}" instead.`;
97
+ }
98
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterMobile = filterMobile;
4
+ const device_cache_1 = require("../../lib/device-cache");
5
+ const version_resolver_1 = require("./version-resolver");
6
+ const fuzzy_1 = require("../../lib/fuzzy");
7
+ // Extract all mobile entries from the data
8
+ function getAllMobileEntries(data) {
9
+ return data.mobile.flatMap((grp) => grp.devices.map((d) => ({
10
+ os: grp.os,
11
+ os_version: d.os_version,
12
+ display_name: d.display_name,
13
+ notes: "",
14
+ })));
15
+ }
16
+ // Filter entries by OS
17
+ function filterByOS(entries, os) {
18
+ const candidates = entries.filter((d) => d.os === os);
19
+ if (!candidates.length)
20
+ throw new Error(`No mobile OS entries for "${os}".`);
21
+ return candidates;
22
+ }
23
+ // Find matching device with exact match validation
24
+ function findMatchingDevice(entries, deviceName, os) {
25
+ const matches = (0, fuzzy_1.customFuzzySearch)(entries, ["display_name"], deviceName, 5);
26
+ if (!matches.length)
27
+ throw new Error(`No devices matching "${deviceName}" on ${os}.`);
28
+ const exact = matches.find((m) => m.display_name.toLowerCase() === deviceName.toLowerCase());
29
+ if (!exact) {
30
+ const names = matches.map((m) => m.display_name).join(", ");
31
+ throw new Error(`Alternative Device/Device's found : ${names}. Please Select one.`);
32
+ }
33
+ const result = entries.filter((d) => d.display_name === exact.display_name);
34
+ if (!result.length)
35
+ throw new Error(`No device "${exact.display_name}" on ${os}.`);
36
+ return result;
37
+ }
38
+ // Find the appropriate OS version
39
+ function findOSVersion(entries, requestedVersion) {
40
+ const versions = entries.map((d) => d.os_version);
41
+ const chosenVersion = (0, version_resolver_1.resolveVersion)(requestedVersion, versions);
42
+ const result = entries.filter((d) => d.os_version === chosenVersion);
43
+ if (!result.length)
44
+ throw new Error(`No entry for OS version "${requestedVersion}".`);
45
+ return { entries: result, chosenVersion };
46
+ }
47
+ // Create version note if needed
48
+ function createVersionNote(requestedVersion, actualVersion) {
49
+ if (actualVersion !== requestedVersion &&
50
+ requestedVersion !== "latest" &&
51
+ requestedVersion !== "oldest") {
52
+ return `Note: Os version ${requestedVersion} was not found. Using ${actualVersion} instead.`;
53
+ }
54
+ return "";
55
+ }
56
+ async function filterMobile(args) {
57
+ const data = await (0, device_cache_1.getDevicesAndBrowsers)("live");
58
+ const allEntries = getAllMobileEntries(data);
59
+ const osCandidates = filterByOS(allEntries, args.os);
60
+ const deviceCandidates = findMatchingDevice(osCandidates, args.device, args.os);
61
+ const { entries: versionCandidates, chosenVersion } = findOSVersion(deviceCandidates, args.osVersion);
62
+ const final = versionCandidates[0];
63
+ final.notes = createVersionNote(args.osVersion, chosenVersion);
64
+ return final;
65
+ }
@@ -4,35 +4,77 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startBrowserSession = startBrowserSession;
7
- const utils_1 = require("../../lib/utils");
8
7
  const logger_1 = __importDefault(require("../../logger"));
9
8
  const child_process_1 = __importDefault(require("child_process"));
9
+ const desktop_filter_1 = require("./desktop-filter");
10
+ const mobile_filter_1 = require("./mobile-filter");
11
+ const types_1 = require("./types");
12
+ const local_1 = require("../../lib/local");
13
+ /**
14
+ * Prepares local tunnel setup based on URL type
15
+ */
16
+ async function prepareLocalTunnel(url) {
17
+ const isLocal = (0, local_1.isLocalURL)(url);
18
+ if (isLocal) {
19
+ await (0, local_1.ensureLocalBinarySetup)();
20
+ }
21
+ else {
22
+ await (0, local_1.killExistingBrowserStackLocalProcesses)();
23
+ }
24
+ return isLocal;
25
+ }
26
+ /**
27
+ * Entrypoint: detects platformType & delegates.
28
+ */
10
29
  async function startBrowserSession(args) {
11
- // Sanitize all input parameters
12
- const sanitizedArgs = {
13
- browser: (0, utils_1.sanitizeUrlParam)(args.browser),
14
- os: (0, utils_1.sanitizeUrlParam)(args.os),
15
- osVersion: (0, utils_1.sanitizeUrlParam)(args.osVersion),
16
- url: (0, utils_1.sanitizeUrlParam)(args.url),
17
- browserVersion: (0, utils_1.sanitizeUrlParam)(args.browserVersion),
18
- isLocal: args.isLocal,
19
- };
20
- // Construct URL with encoded parameters
30
+ const entry = args.platformType === types_1.PlatformType.DESKTOP
31
+ ? await (0, desktop_filter_1.filterDesktop)(args)
32
+ : await (0, mobile_filter_1.filterMobile)(args);
33
+ const isLocal = await prepareLocalTunnel(args.url);
34
+ const url = args.platformType === types_1.PlatformType.DESKTOP
35
+ ? buildDesktopUrl(args, entry, isLocal)
36
+ : buildMobileUrl(args, entry, isLocal);
37
+ openBrowser(url);
38
+ return entry.notes ? `${url}, ${entry.notes}` : url;
39
+ }
40
+ function buildDesktopUrl(args, e, isLocal) {
21
41
  const params = new URLSearchParams({
22
- os: sanitizedArgs.os,
23
- os_version: sanitizedArgs.osVersion,
24
- browser: sanitizedArgs.browser,
25
- browser_version: sanitizedArgs.browserVersion,
42
+ os: e.os,
43
+ os_version: e.os_version,
44
+ browser: e.browser,
45
+ browser_version: e.browser_version,
46
+ url: args.url,
26
47
  scale_to_fit: "true",
27
- url: sanitizedArgs.url,
28
48
  resolution: "responsive-mode",
29
49
  speed: "1",
30
- local: sanitizedArgs.isLocal ? "true" : "false",
50
+ local: isLocal ? "true" : "false",
51
+ start: "true",
52
+ });
53
+ return `https://live.browserstack.com/dashboard#${params.toString()}`;
54
+ }
55
+ function buildMobileUrl(args, d, isLocal) {
56
+ const os_map = {
57
+ android: "Android",
58
+ ios: "iOS",
59
+ winphone: "Winphone",
60
+ };
61
+ const os = os_map[d.os] || d.os;
62
+ const params = new URLSearchParams({
63
+ os: os,
64
+ os_version: d.os_version,
65
+ device: d.display_name,
66
+ device_browser: args.browser,
67
+ url: args.url,
68
+ scale_to_fit: "true",
69
+ speed: "1",
70
+ local: isLocal ? "true" : "false",
31
71
  start: "true",
32
72
  });
33
- const launchUrl = `https://live.browserstack.com/dashboard#${params.toString()}`;
73
+ return `https://live.browserstack.com/dashboard#${params.toString()}`;
74
+ }
75
+ // ——— Open a browser window ———
76
+ function openBrowser(launchUrl) {
34
77
  try {
35
- // Use platform-specific commands with proper escaping
36
78
  const command = process.platform === "darwin"
37
79
  ? ["open", launchUrl]
38
80
  : process.platform === "win32"
@@ -43,16 +85,10 @@ async function startBrowserSession(args) {
43
85
  stdio: "ignore",
44
86
  detached: true,
45
87
  });
46
- // Handle process errors
47
- child.on("error", (error) => {
48
- logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
49
- });
50
- // Unref the child process to allow the parent to exit
88
+ child.on("error", (err) => logger_1.default.error(`Failed to open browser: ${err}. URL: ${launchUrl}`));
51
89
  child.unref();
52
- return launchUrl;
53
90
  }
54
- catch (error) {
55
- logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
56
- return launchUrl;
91
+ catch (err) {
92
+ logger_1.default.error(`Failed to launch browser: ${err}. URL: ${launchUrl}`);
57
93
  }
58
94
  }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PlatformType = void 0;
4
+ var PlatformType;
5
+ (function (PlatformType) {
6
+ PlatformType["DESKTOP"] = "desktop";
7
+ PlatformType["MOBILE"] = "mobile";
8
+ })(PlatformType || (exports.PlatformType = PlatformType = {}));
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVersion = resolveVersion;
4
+ /**
5
+ * If req === "latest" or "oldest", returns max/min numeric (or lex)
6
+ * Else if exact match, returns that
7
+ * Else picks the numerically closest (or first)
8
+ */
9
+ function resolveVersion(requested, available) {
10
+ // strip duplicates & sort
11
+ const uniq = Array.from(new Set(available));
12
+ // pick min/max
13
+ if (requested === "latest" || requested === "oldest") {
14
+ // try numeric
15
+ const nums = uniq
16
+ .map((v) => ({ v, n: parseFloat(v) }))
17
+ .filter((x) => !isNaN(x.n))
18
+ .sort((a, b) => a.n - b.n);
19
+ if (nums.length) {
20
+ return requested === "latest" ? nums[nums.length - 1].v : nums[0].v;
21
+ }
22
+ // fallback lex
23
+ const lex = uniq.slice().sort();
24
+ return requested === "latest" ? lex[lex.length - 1] : lex[0];
25
+ }
26
+ // exact?
27
+ if (uniq.includes(requested)) {
28
+ return requested;
29
+ }
30
+ // try closest numeric
31
+ const reqNum = parseFloat(requested);
32
+ const nums = uniq
33
+ .map((v) => ({ v, n: parseFloat(v) }))
34
+ .filter((x) => !isNaN(x.n));
35
+ if (!isNaN(reqNum) && nums.length) {
36
+ let best = nums[0], bestDiff = Math.abs(nums[0].n - reqNum);
37
+ for (const x of nums) {
38
+ const d = Math.abs(x.n - reqNum);
39
+ if (d < bestDiff) {
40
+ best = x;
41
+ bestDiff = d;
42
+ }
43
+ }
44
+ return best.v;
45
+ }
46
+ // final fallback
47
+ return uniq[0];
48
+ }
@@ -3,90 +3,111 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.startBrowserLiveSession = startBrowserLiveSession;
7
6
  exports.default = addBrowserLiveTools;
8
7
  const zod_1 = require("zod");
9
- const start_session_1 = require("./live-utils/start-session");
10
8
  const logger_1 = __importDefault(require("../logger"));
11
- const local_1 = require("../lib/local");
9
+ const start_session_1 = require("./live-utils/start-session");
10
+ const types_1 = require("./live-utils/types");
11
+ // Define the schema shape
12
+ const LiveArgsShape = {
13
+ platformType: zod_1.z
14
+ .nativeEnum(types_1.PlatformType)
15
+ .describe("Must be 'desktop' or 'mobile'"),
16
+ desiredURL: zod_1.z.string().url().describe("The URL to test"),
17
+ desiredOS: zod_1.z
18
+ .enum(["Windows", "OS X", "android", "ios", "winphone"])
19
+ .describe("Desktop OS ('Windows' or 'OS X') or mobile OS ('android','ios','winphone')"),
20
+ desiredOSVersion: zod_1.z
21
+ .string()
22
+ .describe("The OS version must be specified as a version number (e.g., '10', '14.0') or as a keyword such as 'latest' or 'oldest'. Normalize variations like 'newest' or 'most recent' to 'latest', and terms like 'earliest' or 'first' to 'oldest'. For macOS, version names (e.g., 'Sequoia') must be used instead of numeric versions."),
23
+ desiredBrowser: zod_1.z
24
+ .enum(["chrome", "firefox", "safari", "edge", "ie"])
25
+ .describe("Browser for desktop (Chrome, IE, Firefox, Safari, Edge)"),
26
+ desiredBrowserVersion: zod_1.z
27
+ .string()
28
+ .optional()
29
+ .describe("Browser version for desktop (e.g. '133.2', 'latest'). If the user says 'latest', 'newest', or similar, normalize it to 'latest'. Likewise, convert terms like 'earliest' or 'oldest' to 'oldest'."),
30
+ desiredDevice: zod_1.z.string().optional().describe("Device name for mobile"),
31
+ };
32
+ const LiveArgsSchema = zod_1.z.object(LiveArgsShape);
12
33
  /**
13
- * Launches a Browser Live Session on BrowserStack.
34
+ * Launches a desktop browser session
14
35
  */
15
- async function startBrowserLiveSession(args) {
16
- if (!args.desiredBrowser) {
17
- throw new Error("You must provide a desiredBrowser.");
18
- }
19
- if (!args.desiredURL) {
20
- throw new Error("You must provide a desiredURL.");
21
- }
22
- if (!args.desiredOS) {
23
- throw new Error("You must provide a desiredOS.");
24
- }
25
- if (!args.desiredOSVersion) {
26
- throw new Error("You must provide a desiredOSVersion.");
27
- }
28
- if (!args.desiredBrowserVersion) {
29
- throw new Error("You must provide a desiredBrowserVersion.");
30
- }
31
- // Validate URL format
32
- try {
33
- new URL(args.desiredURL);
34
- }
35
- catch (error) {
36
- logger_1.default.error("Invalid URL format: %s", error);
37
- throw new Error("The provided URL is invalid.");
38
- }
39
- const isLocal = (0, local_1.isLocalURL)(args.desiredURL);
40
- if (isLocal) {
41
- await (0, local_1.ensureLocalBinarySetup)();
42
- }
43
- else {
44
- await (0, local_1.killExistingBrowserStackLocalProcesses)();
45
- }
46
- const launchUrl = await (0, start_session_1.startBrowserSession)({
47
- browser: args.desiredBrowser,
36
+ async function launchDesktopSession(args) {
37
+ if (!args.desiredBrowser)
38
+ throw new Error("You must provide a desiredBrowser");
39
+ if (!args.desiredBrowserVersion)
40
+ throw new Error("You must provide a desiredBrowserVersion");
41
+ return (0, start_session_1.startBrowserSession)({
42
+ platformType: types_1.PlatformType.DESKTOP,
43
+ url: args.desiredURL,
48
44
  os: args.desiredOS,
49
45
  osVersion: args.desiredOSVersion,
50
- url: args.desiredURL,
46
+ browser: args.desiredBrowser,
51
47
  browserVersion: args.desiredBrowserVersion,
52
- isLocal,
53
48
  });
54
- return {
55
- content: [
56
- {
57
- type: "text",
58
- text: `Successfully started a browser session. If a browser window did not open automatically, use the following URL to start the session: ${launchUrl}`,
59
- },
60
- ],
61
- };
49
+ }
50
+ /**
51
+ * Launches a mobile browser session
52
+ */
53
+ async function launchMobileSession(args) {
54
+ if (!args.desiredDevice)
55
+ throw new Error("You must provide a desiredDevice");
56
+ return (0, start_session_1.startBrowserSession)({
57
+ platformType: types_1.PlatformType.MOBILE,
58
+ browser: args.desiredBrowser,
59
+ url: args.desiredURL,
60
+ os: args.desiredOS,
61
+ osVersion: args.desiredOSVersion,
62
+ device: args.desiredDevice,
63
+ });
64
+ }
65
+ /**
66
+ * Handles the core logic for running a browser session
67
+ */
68
+ async function runBrowserSession(rawArgs) {
69
+ // Validate and narrow
70
+ 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
+ }
62
98
  }
63
99
  function addBrowserLiveTools(server) {
64
- server.tool("runBrowserLiveSession", "Use this tool when user wants to manually check their website on a particular browser and OS combination using BrowserStack's cloud infrastructure. Can be used to debug layout issues, compatibility problems, etc.", {
65
- desiredBrowser: zod_1.z
66
- .enum(["Chrome", "IE", "Firefox", "Safari", "Edge"])
67
- .describe("The browser to run the test on. Example: 'Chrome', 'IE', 'Safari', 'Edge'. Always ask the user for the browser they want to use, do not assume it."),
68
- desiredOSVersion: zod_1.z
69
- .string()
70
- .describe("The OS version to run the browser on. Example: '10' for Windows, '12' for macOS, '14' for iOS"),
71
- desiredOS: zod_1.z
72
- .enum(["Windows", "OS X"])
73
- .describe("The operating system to run the browser on. Example: 'Windows', 'OS X'"),
74
- desiredURL: zod_1.z
75
- .string()
76
- .describe("The URL of the website to test. This can be a local URL (e.g., http://localhost:3000) or a public URL. Always ask the user for the URL, do not assume it."),
77
- desiredBrowserVersion: zod_1.z
78
- .string()
79
- .describe("The version of the browser to use. Example: 133.0, 134.0, 87.0"),
80
- }, async (args) => {
100
+ server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
81
101
  try {
82
- return startBrowserLiveSession(args);
102
+ const result = await runBrowserSession(args);
103
+ return result;
83
104
  }
84
105
  catch (error) {
85
106
  return {
86
107
  content: [
87
108
  {
88
109
  type: "text",
89
- text: `Failed to start a browser live session. Error: ${error}. Please open an issue on GitHub if the problem persists`,
110
+ text: `Failed to start a browser live session. Error: ${error}`,
90
111
  isError: true,
91
112
  },
92
113
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -1,82 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SUPPORTED_LIVE_CONFIGURATIONS = void 0;
4
- exports.SUPPORTED_LIVE_CONFIGURATIONS = {
5
- android: {
6
- chrome: {
7
- version: "100",
8
- },
9
- firefox: {
10
- version: "100",
11
- },
12
- safari: {
13
- version: "100",
14
- },
15
- "samsung browser": {
16
- version: "100",
17
- },
18
- "internet-explorer": {
19
- version: "100",
20
- },
21
- },
22
- ios: {
23
- chrome: {
24
- version: "100",
25
- },
26
- firefox: {
27
- version: "100",
28
- },
29
- edge: {
30
- version: "100",
31
- },
32
- safari: {
33
- version: "100",
34
- },
35
- "samsung browser": {
36
- version: "100",
37
- },
38
- "internet-explorer": {
39
- version: "100",
40
- },
41
- },
42
- windows: {
43
- chrome: {
44
- version: "100",
45
- },
46
- firefox: {
47
- version: "100",
48
- },
49
- edge: {
50
- version: "100",
51
- },
52
- safari: {
53
- version: "100",
54
- },
55
- "samsung browser": {
56
- version: "100",
57
- },
58
- "internet-explorer": {
59
- version: "100",
60
- },
61
- },
62
- macos: {
63
- chrome: {
64
- version: "100",
65
- },
66
- firefox: {
67
- version: "100",
68
- },
69
- edge: {
70
- version: "100",
71
- },
72
- safari: {
73
- version: "100",
74
- },
75
- "samsung browser": {
76
- version: "100",
77
- },
78
- "internet-explorer": {
79
- version: "100",
80
- },
81
- },
82
- };
@@ -1,33 +0,0 @@
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.getAppLiveData = getAppLiveData;
7
- const fs_1 = __importDefault(require("fs"));
8
- const os_1 = __importDefault(require("os"));
9
- const path_1 = __importDefault(require("path"));
10
- const CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".browserstack", "app_live_cache");
11
- const CACHE_FILE = path_1.default.join(CACHE_DIR, "app_live.json");
12
- const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
13
- /**
14
- * Fetches and caches the App Live devices JSON with a 1-day TTL.
15
- */
16
- async function getAppLiveData() {
17
- if (!fs_1.default.existsSync(CACHE_DIR)) {
18
- fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
19
- }
20
- if (fs_1.default.existsSync(CACHE_FILE)) {
21
- const stats = fs_1.default.statSync(CACHE_FILE);
22
- if (Date.now() - stats.mtimeMs < TTL_MS) {
23
- return JSON.parse(fs_1.default.readFileSync(CACHE_FILE, "utf8"));
24
- }
25
- }
26
- const response = await fetch("https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json");
27
- if (!response.ok) {
28
- throw new Error(`Failed to fetch app live list: ${response.statusText}`);
29
- }
30
- const data = await response.json();
31
- fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(data), "utf8");
32
- return data;
33
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });