@browserstack/mcp-server 2.0.0 → 2.1.1-beta.1

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.
@@ -11,4 +11,14 @@
11
11
  // describe text in register.ts, and the two a11y handoffs in tool-handoff.ts — which stop
12
12
  // pointing here precisely because the call would now be rejected.
13
13
  export const PRODUCTS = ["tm", "tra"];
14
- export const ASK_STATUSES = ["ok", "blocked", "error", "rate_limited"];
14
+ export const ASK_STATUSES = [
15
+ "ok",
16
+ "blocked",
17
+ "error",
18
+ "rate_limited",
19
+ // Deferred only: the run reached an ask and is PARKED, not finished. Distinct from
20
+ // `blocked` (a human said no) and from `error` (nothing is waiting) — this one is the
21
+ // only status a caller is invited to come back from, and the only one carrying a
22
+ // `run_id`.
23
+ "needs_approval",
24
+ ];
@@ -2,6 +2,7 @@ import { assertOkResponse, maybeCompressBase64 } from "../../lib/utils.js";
2
2
  import { SessionType } from "../../lib/constants.js";
3
3
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
4
4
  import { apiClient } from "../../lib/apiClient.js";
5
+ import globalConfig from "../../config.js";
5
6
  async function extractScreenshotUrls(sessionId, sessionType, config) {
6
7
  const authString = getBrowserStackAuth(config);
7
8
  const auth = Buffer.from(authString).toString("base64");
@@ -36,23 +37,24 @@ async function extractScreenshotUrls(sessionId, sessionType, config) {
36
37
  }
37
38
  return urls;
38
39
  }
40
+ async function fetchAndCompress(url) {
41
+ const response = await apiClient.get({ url, responseType: "arraybuffer" });
42
+ // Axios returns response.data as a Buffer for binary data
43
+ const base64 = Buffer.from(response.data).toString("base64");
44
+ return { url, base64: await maybeCompressBase64(base64) };
45
+ }
39
46
  //Converts screenshot URLs to base64 encoded images
40
47
  async function convertUrlsToBase64(urls) {
41
- const screenshots = await Promise.all(urls.map(async (url) => {
42
- const response = await apiClient.get({
43
- url,
44
- responseType: "arraybuffer",
45
- });
46
- // Axios returns response.data as a Buffer for binary data
47
- const base64 = Buffer.from(response.data).toString("base64");
48
- // Compress the base64 image if needed
49
- const compressedBase64 = await maybeCompressBase64(base64);
50
- return {
51
- url,
52
- base64: compressedBase64,
53
- };
54
- }));
55
- return screenshots;
48
+ // Hosted pods are memory-constrained: process one image at a time. Local runs
49
+ // keep the concurrent path.
50
+ if (globalConfig.REMOTE_MCP) {
51
+ const screenshots = [];
52
+ for (const url of urls) {
53
+ screenshots.push(await fetchAndCompress(url));
54
+ }
55
+ return screenshots;
56
+ }
57
+ return Promise.all(urls.map(fetchAndCompress));
56
58
  }
57
59
  //Fetches and converts screenshot URLs to base64 encoded images
58
60
  export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate, config) {
@@ -25,13 +25,13 @@ const RULES = [
25
25
  },
26
26
  {
27
27
  name: "email",
28
- pattern: /\b[\w.%+-]+@[\w.-]+\.[a-z]{2,}\b/gi,
28
+ pattern: /\b[\w.%+-]{1,128}@[\w.-]{1,128}\.[a-z]{2,24}\b/gi,
29
29
  replacement: "[email]",
30
30
  },
31
31
  // JWT: three base64url segments.
32
32
  {
33
33
  name: "jwt",
34
- pattern: /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}\b/g,
34
+ pattern: /\beyJ[\w-]{8,1024}\.[\w-]{8,1024}\.[\w-]{8,1024}\b/g,
35
35
  replacement: "[token]",
36
36
  },
37
37
  // A long opaque run — access keys, API keys, session ids. Requires BOTH a digit and a
@@ -41,7 +41,7 @@ const RULES = [
41
41
  // wording a product uses is exactly the assumption that fails.
42
42
  {
43
43
  name: "opaque_token",
44
- pattern: /\b(?=[A-Za-z0-9_-]*\d)(?=[A-Za-z0-9_-]*[A-Za-z])[A-Za-z0-9_-]{20,}\b/g,
44
+ pattern: /\b(?=[A-Za-z0-9_-]{0,256}\d)(?=[A-Za-z0-9_-]{0,256}[A-Za-z])[A-Za-z0-9_-]{20,256}\b/g,
45
45
  replacement: "[token]",
46
46
  },
47
47
  {
@@ -53,7 +53,7 @@ const RULES = [
53
53
  // separator or a `+`, so a bare digit run falls to long_number instead.
54
54
  {
55
55
  name: "phone",
56
- pattern: /(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,5}[\s.-]\d{3,5}(?:[\s.-]\d{2,5})?\b/g,
56
+ pattern: /(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\b\d{3,5}[\s.-]\d{3,5}(?:[\s.-]\d{2,5})?\b/g,
57
57
  replacement: "[phone]",
58
58
  },
59
59
  // Long bare digit runs: account ids, card numbers, unseparated phone numbers.
@@ -81,7 +81,9 @@ export function redact(text) {
81
81
  try {
82
82
  if (typeof text !== "string")
83
83
  return undefined;
84
- let out = text.replace(/\s+/g, " ").trim();
84
+ // Slice before the rules run; a multi-KB digit run makes them backtrack.
85
+ const SCAN_LIMIT = MAX_LENGTH * 8;
86
+ let out = text.slice(0, SCAN_LIMIT).replace(/\s+/g, " ").trim();
85
87
  if (!out)
86
88
  return undefined;
87
89
  for (const rule of RULES)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.1-beta.1",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",