@browserstack/mcp-server 1.2.19 → 1.2.20-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.
package/dist/lib/apiClient.js
CHANGED
|
@@ -55,7 +55,6 @@ function getAxiosAgent() {
|
|
|
55
55
|
host: proxyHost,
|
|
56
56
|
port: Number(proxyPort),
|
|
57
57
|
ca,
|
|
58
|
-
rejectUnauthorized: false, // Set to true if you want strict SSL
|
|
59
58
|
});
|
|
60
59
|
}
|
|
61
60
|
else {
|
|
@@ -67,7 +66,6 @@ function getAxiosAgent() {
|
|
|
67
66
|
// CA only
|
|
68
67
|
return new https.Agent({
|
|
69
68
|
ca: fs.readFileSync(caCertPath),
|
|
70
|
-
rejectUnauthorized: false, // Set to true for strict SSL
|
|
71
69
|
});
|
|
72
70
|
}
|
|
73
71
|
// Default agent (no proxy, no CA)
|
|
@@ -5,7 +5,6 @@ import { uploadApp } from "./upload-app.js";
|
|
|
5
5
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
6
6
|
import { findDeviceByName } from "./device-search.js";
|
|
7
7
|
import { pickVersion } from "./version-utils.js";
|
|
8
|
-
import childProcess from "child_process";
|
|
9
8
|
import envConfig from "../../config.js";
|
|
10
9
|
/**
|
|
11
10
|
* Start an App Live session: filter, select, upload, and open.
|
|
@@ -69,33 +68,44 @@ export async function startSession(args, options) {
|
|
|
69
68
|
});
|
|
70
69
|
const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`;
|
|
71
70
|
if (!envConfig.REMOTE_MCP) {
|
|
72
|
-
|
|
71
|
+
const openCommand = getOpenBrowserCommand(launchUrl);
|
|
72
|
+
if (openCommand) {
|
|
73
|
+
return [
|
|
74
|
+
`App Live session URL: ${launchUrl}${note}`,
|
|
75
|
+
``,
|
|
76
|
+
`To open the session in the default browser, run:`,
|
|
77
|
+
` ${openCommand}`,
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
73
80
|
}
|
|
74
81
|
return launchUrl + note;
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
84
|
+
* Returns the platform-appropriate shell command to open `launchUrl` in the
|
|
85
|
+
* default browser, or null if the URL is not a trusted BrowserStack URL.
|
|
86
|
+
*
|
|
87
|
+
* The command is returned to the MCP client so the host agent can prompt the
|
|
88
|
+
* user before executing it. The server itself never spawns a process, which
|
|
89
|
+
* eliminates the command-injection surface entirely.
|
|
80
90
|
*/
|
|
81
|
-
function
|
|
91
|
+
function getOpenBrowserCommand(launchUrl) {
|
|
92
|
+
let parsed;
|
|
82
93
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
|
|
89
|
-
const child = childProcess.spawn(command[0], command.slice(1), {
|
|
90
|
-
stdio: "ignore",
|
|
91
|
-
detached: true,
|
|
92
|
-
});
|
|
93
|
-
child.on("error", (error) => {
|
|
94
|
-
logger.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
|
|
95
|
-
});
|
|
96
|
-
child.unref();
|
|
94
|
+
parsed = new URL(launchUrl);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
logger.error(`Refusing to surface malformed URL: ${launchUrl}`);
|
|
98
|
+
return null;
|
|
97
99
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
if (parsed.protocol !== "https:" ||
|
|
101
|
+
!/(^|\.)browserstack\.com$/i.test(parsed.hostname)) {
|
|
102
|
+
logger.error(`Refusing to surface untrusted URL: ${launchUrl}`);
|
|
103
|
+
return null;
|
|
100
104
|
}
|
|
105
|
+
const quoted = `"${parsed.toString()}"`;
|
|
106
|
+
if (process.platform === "darwin")
|
|
107
|
+
return `open ${quoted}`;
|
|
108
|
+
if (process.platform === "win32")
|
|
109
|
+
return `cmd /c start "" ${quoted}`;
|
|
110
|
+
return `xdg-open ${quoted}`;
|
|
101
111
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logger from "../../logger.js";
|
|
2
|
-
import childProcess from "child_process";
|
|
3
2
|
import { filterDesktop } from "./desktop-filter.js";
|
|
4
3
|
import { filterMobile } from "./mobile-filter.js";
|
|
5
4
|
import { PlatformType, } from "./types.js";
|
|
@@ -39,10 +38,19 @@ export async function startBrowserSession(args, config) {
|
|
|
39
38
|
const url = args.platformType === PlatformType.DESKTOP
|
|
40
39
|
? buildDesktopUrl(args, entry, isLocal)
|
|
41
40
|
: buildMobileUrl(args, entry, isLocal);
|
|
41
|
+
const note = entry.notes ? `, ${entry.notes}` : "";
|
|
42
42
|
if (!envConfig.REMOTE_MCP) {
|
|
43
|
-
|
|
43
|
+
const openCommand = getOpenBrowserCommand(url);
|
|
44
|
+
if (openCommand) {
|
|
45
|
+
return [
|
|
46
|
+
`Live session URL: ${url}${note}`,
|
|
47
|
+
``,
|
|
48
|
+
`To open the session in the default browser, run:`,
|
|
49
|
+
` ${openCommand}`,
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
|
-
return
|
|
53
|
+
return `${url}${note}`;
|
|
46
54
|
}
|
|
47
55
|
function buildDesktopUrl(args, e, isLocal) {
|
|
48
56
|
const params = new URLSearchParams({
|
|
@@ -79,24 +87,33 @@ function buildMobileUrl(args, d, isLocal) {
|
|
|
79
87
|
});
|
|
80
88
|
return `https://live.browserstack.com/dashboard#${params.toString()}`;
|
|
81
89
|
}
|
|
82
|
-
// ———
|
|
83
|
-
|
|
90
|
+
// ——— Build a browser-open command for the host agent ———
|
|
91
|
+
/**
|
|
92
|
+
* Returns the platform-appropriate shell command to open `launchUrl` in the
|
|
93
|
+
* default browser, or null if the URL is not a trusted BrowserStack URL.
|
|
94
|
+
*
|
|
95
|
+
* The command is returned to the MCP client so the host agent can prompt the
|
|
96
|
+
* user before executing it. The server itself never spawns a process, which
|
|
97
|
+
* eliminates the command-injection surface entirely.
|
|
98
|
+
*/
|
|
99
|
+
function getOpenBrowserCommand(launchUrl) {
|
|
100
|
+
let parsed;
|
|
84
101
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
|
|
91
|
-
const child = childProcess.spawn(command[0], command.slice(1), {
|
|
92
|
-
stdio: "ignore",
|
|
93
|
-
detached: true,
|
|
94
|
-
...(process.platform === "win32" ? { shell: true } : {}),
|
|
95
|
-
});
|
|
96
|
-
child.on("error", (err) => logger.error(`Failed to open browser: ${err}. URL: ${launchUrl}`));
|
|
97
|
-
child.unref();
|
|
102
|
+
parsed = new URL(launchUrl);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
logger.error(`Refusing to surface malformed URL: ${launchUrl}`);
|
|
106
|
+
return null;
|
|
98
107
|
}
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
if (parsed.protocol !== "https:" ||
|
|
109
|
+
!/(^|\.)browserstack\.com$/i.test(parsed.hostname)) {
|
|
110
|
+
logger.error(`Refusing to surface untrusted URL: ${launchUrl}`);
|
|
111
|
+
return null;
|
|
101
112
|
}
|
|
113
|
+
const quoted = `"${parsed.toString()}"`;
|
|
114
|
+
if (process.platform === "darwin")
|
|
115
|
+
return `open ${quoted}`;
|
|
116
|
+
if (process.platform === "win32")
|
|
117
|
+
return `cmd /c start "" ${quoted}`;
|
|
118
|
+
return `xdg-open ${quoted}`;
|
|
102
119
|
}
|