@browserstack/mcp-server 1.1.9 → 1.2.0
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.d.ts +13 -0
- package/dist/config.js +4 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -30
- package/dist/lib/api.d.ts +2 -0
- package/dist/lib/api.js +10 -5
- package/dist/lib/apiClient.d.ts +31 -0
- package/dist/lib/apiClient.js +108 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/device-cache.d.ts +9 -0
- package/dist/lib/device-cache.js +3 -3
- package/dist/lib/error.d.ts +7 -0
- package/dist/lib/fuzzy.d.ts +1 -0
- package/dist/lib/get-auth.d.ts +2 -0
- package/dist/lib/get-auth.js +8 -0
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/instrumentation.d.ts +4 -0
- package/dist/lib/instrumentation.js +8 -7
- package/dist/lib/local.d.ts +3 -0
- package/dist/lib/local.js +3 -3
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/lib/version-resolver.d.ts +6 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +20 -4
- package/dist/oninitialized.d.ts +2 -0
- package/dist/oninitialized.js +2 -7
- package/dist/server-factory.d.ts +3 -0
- package/dist/server-factory.js +37 -0
- package/dist/tools/accessibility.d.ts +3 -0
- package/dist/tools/accessibility.js +17 -10
- package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
- package/dist/tools/accessiblity-utils/accessibility-rag.js +22 -12
- package/dist/tools/accessiblity-utils/report-fetcher.d.ts +8 -0
- package/dist/tools/accessiblity-utils/report-fetcher.js +26 -16
- package/dist/tools/accessiblity-utils/report-parser.d.ts +23 -0
- package/dist/tools/accessiblity-utils/report-parser.js +3 -3
- package/dist/tools/accessiblity-utils/scanner.d.ts +25 -0
- package/dist/tools/accessiblity-utils/scanner.js +43 -24
- package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
- package/dist/tools/appautomate-utils/appautomate.js +63 -38
- package/dist/tools/appautomate-utils/types.d.ts +5 -0
- package/dist/tools/appautomate.d.ts +3 -0
- package/dist/tools/appautomate.js +20 -19
- package/dist/tools/applive-utils/device-search.d.ts +6 -0
- package/dist/tools/applive-utils/start-session.d.ts +15 -0
- package/dist/tools/applive-utils/start-session.js +11 -4
- package/dist/tools/applive-utils/types.d.ts +7 -0
- package/dist/tools/applive-utils/upload-app.d.ts +5 -0
- package/dist/tools/applive-utils/upload-app.js +8 -12
- package/dist/tools/applive-utils/version-utils.d.ts +4 -0
- package/dist/tools/applive.d.ts +13 -0
- package/dist/tools/applive.js +6 -6
- package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
- package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
- package/dist/tools/automate.d.ts +9 -0
- package/dist/tools/automate.js +6 -6
- package/dist/tools/bstack-sdk.d.ts +17 -0
- package/dist/tools/bstack-sdk.js +12 -7
- package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
- package/dist/tools/failurelogs-utils/app-automate.js +29 -11
- package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
- package/dist/tools/failurelogs-utils/automate.js +27 -12
- package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
- package/dist/tools/getFailureLogs.d.ts +14 -0
- package/dist/tools/getFailureLogs.js +11 -11
- package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
- package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
- package/dist/tools/live-utils/start-session.d.ts +6 -0
- package/dist/tools/live-utils/start-session.js +18 -5
- package/dist/tools/live-utils/types.d.ts +33 -0
- package/dist/tools/live.d.ts +3 -0
- package/dist/tools/live.js +11 -11
- package/dist/tools/observability.d.ts +5 -0
- package/dist/tools/observability.js +14 -11
- package/dist/tools/sdk-utils/commands.d.ts +3 -0
- package/dist/tools/sdk-utils/commands.js +5 -4
- package/dist/tools/sdk-utils/constants.d.ts +2 -0
- package/dist/tools/sdk-utils/constants.js +82 -21
- package/dist/tools/sdk-utils/instructions.d.ts +6 -0
- package/dist/tools/sdk-utils/instructions.js +8 -6
- package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
- package/dist/tools/sdk-utils/percy/constants.js +1 -0
- package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
- package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
- package/dist/tools/sdk-utils/types.d.ts +39 -0
- package/dist/tools/sdk-utils/types.js +1 -0
- package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
- package/dist/tools/selfheal-utils/selfheal.js +10 -6
- package/dist/tools/selfheal.d.ts +7 -0
- package/dist/tools/selfheal.js +6 -6
- package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
- package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
- package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
- package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
- package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
- package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
- package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
- package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
- package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
- package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
- package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
- package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
- package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
- package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
- package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
- package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
- package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
- package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
- package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
- package/dist/tools/testmanagement-utils/upload-file.js +8 -6
- package/dist/tools/testmanagement.d.ts +60 -0
- package/dist/tools/testmanagement.js +51 -53
- package/package.json +1 -1
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USE_OWN_LOCAL_BINARY_PROCESS:
|
|
3
|
+
* If true, the system will not start a new local binary process, but will use the user's own process.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Config {
|
|
6
|
+
readonly DEV_MODE: boolean;
|
|
7
|
+
readonly browserstackLocalOptions: Record<string, any>;
|
|
8
|
+
readonly USE_OWN_LOCAL_BINARY_PROCESS: boolean;
|
|
9
|
+
readonly REMOTE_MCP: boolean;
|
|
10
|
+
constructor(DEV_MODE: boolean, browserstackLocalOptions: Record<string, any>, USE_OWN_LOCAL_BINARY_PROCESS: boolean, REMOTE_MCP: boolean);
|
|
11
|
+
}
|
|
12
|
+
declare const config: Config;
|
|
13
|
+
export default config;
|
package/dist/config.js
CHANGED
|
@@ -33,18 +33,16 @@ for (const key of BROWSERSTACK_LOCAL_OPTION_KEYS) {
|
|
|
33
33
|
* If true, the system will not start a new local binary process, but will use the user's own process.
|
|
34
34
|
*/
|
|
35
35
|
export class Config {
|
|
36
|
-
browserstackUsername;
|
|
37
|
-
browserstackAccessKey;
|
|
38
36
|
DEV_MODE;
|
|
39
37
|
browserstackLocalOptions;
|
|
40
38
|
USE_OWN_LOCAL_BINARY_PROCESS;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.browserstackAccessKey = browserstackAccessKey;
|
|
39
|
+
REMOTE_MCP;
|
|
40
|
+
constructor(DEV_MODE, browserstackLocalOptions, USE_OWN_LOCAL_BINARY_PROCESS, REMOTE_MCP) {
|
|
44
41
|
this.DEV_MODE = DEV_MODE;
|
|
45
42
|
this.browserstackLocalOptions = browserstackLocalOptions;
|
|
46
43
|
this.USE_OWN_LOCAL_BINARY_PROCESS = USE_OWN_LOCAL_BINARY_PROCESS;
|
|
44
|
+
this.REMOTE_MCP = REMOTE_MCP;
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
|
-
const config = new Config(process.env.
|
|
47
|
+
const config = new Config(process.env.DEV_MODE === "true", browserstackLocalOptions, process.env.USE_OWN_LOCAL_BINARY_PROCESS === "true", process.env.REMOTE_MCP === "true");
|
|
50
48
|
export default config;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,43 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { createRequire } from "module";
|
|
5
4
|
const require = createRequire(import.meta.url);
|
|
6
5
|
const packageJson = require("../package.json");
|
|
7
6
|
import "dotenv/config";
|
|
8
7
|
import logger from "./logger.js";
|
|
9
|
-
import
|
|
10
|
-
import addAppLiveTools from "./tools/applive.js";
|
|
11
|
-
import addBrowserLiveTools from "./tools/live.js";
|
|
12
|
-
import addAccessibilityTools from "./tools/accessibility.js";
|
|
13
|
-
import addTestManagementTools from "./tools/testmanagement.js";
|
|
14
|
-
import addAppAutomationTools from "./tools/appautomate.js";
|
|
15
|
-
import addFailureLogsTools from "./tools/getFailureLogs.js";
|
|
16
|
-
import addAutomateTools from "./tools/automate.js";
|
|
17
|
-
import addSelfHealTools from "./tools/selfheal.js";
|
|
18
|
-
import { setupOnInitialized } from "./oninitialized.js";
|
|
19
|
-
function registerTools(server) {
|
|
20
|
-
addAccessibilityTools(server);
|
|
21
|
-
addSDKTools(server);
|
|
22
|
-
addAppLiveTools(server);
|
|
23
|
-
addBrowserLiveTools(server);
|
|
24
|
-
addTestManagementTools(server);
|
|
25
|
-
addAppAutomationTools(server);
|
|
26
|
-
addFailureLogsTools(server);
|
|
27
|
-
addAutomateTools(server);
|
|
28
|
-
addSelfHealTools(server);
|
|
29
|
-
}
|
|
30
|
-
// Create an MCP server
|
|
31
|
-
const server = new McpServer({
|
|
32
|
-
name: "BrowserStack MCP Server",
|
|
33
|
-
version: packageJson.version,
|
|
34
|
-
});
|
|
35
|
-
setupOnInitialized(server);
|
|
36
|
-
registerTools(server);
|
|
8
|
+
import { createMcpServer } from "./server-factory.js";
|
|
37
9
|
async function main() {
|
|
38
10
|
logger.info("Launching BrowserStack MCP server, version %s", packageJson.version);
|
|
39
|
-
|
|
11
|
+
const remoteMCP = process.env.REMOTE_MCP === "true";
|
|
12
|
+
if (remoteMCP) {
|
|
13
|
+
logger.info("Running in remote MCP mode");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const username = process.env.BROWSERSTACK_USERNAME;
|
|
17
|
+
const accessKey = process.env.BROWSERSTACK_ACCESS_KEY;
|
|
18
|
+
if (!username) {
|
|
19
|
+
throw new Error("BROWSERSTACK_USERNAME environment variable is required");
|
|
20
|
+
}
|
|
21
|
+
if (!accessKey) {
|
|
22
|
+
throw new Error("BROWSERSTACK_ACCESS_KEY environment variable is required");
|
|
23
|
+
}
|
|
40
24
|
const transport = new StdioServerTransport();
|
|
25
|
+
const server = createMcpServer({
|
|
26
|
+
"browserstack-username": username,
|
|
27
|
+
"browserstack-access-key": accessKey,
|
|
28
|
+
});
|
|
41
29
|
await server.connect(transport);
|
|
42
30
|
}
|
|
43
31
|
main().catch(console.error);
|
|
@@ -45,3 +33,5 @@ main().catch(console.error);
|
|
|
45
33
|
process.on("exit", () => {
|
|
46
34
|
logger.flush();
|
|
47
35
|
});
|
|
36
|
+
export { createMcpServer } from "./server-factory.js";
|
|
37
|
+
export { setLogger } from "./logger.js";
|
package/dist/lib/api.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { getBrowserStackAuth } from "./get-auth.js";
|
|
2
|
+
import { apiClient } from "./apiClient.js";
|
|
3
|
+
export async function getLatestO11YBuildInfo(buildName, projectName, config) {
|
|
3
4
|
const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
|
|
4
|
-
const
|
|
5
|
+
const authString = getBrowserStackAuth(config);
|
|
6
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
7
|
+
const buildsResponse = await apiClient.get({
|
|
8
|
+
url: buildsUrl,
|
|
5
9
|
headers: {
|
|
6
|
-
Authorization: `Basic ${
|
|
10
|
+
Authorization: `Basic ${auth}`,
|
|
7
11
|
},
|
|
12
|
+
raise_error: false,
|
|
8
13
|
});
|
|
9
14
|
if (!buildsResponse.ok) {
|
|
10
15
|
if (buildsResponse.statusText === "Unauthorized") {
|
|
@@ -12,5 +17,5 @@ export async function getLatestO11YBuildInfo(buildName, projectName) {
|
|
|
12
17
|
}
|
|
13
18
|
throw new Error(`Failed to fetch builds: ${buildsResponse.statusText}`);
|
|
14
19
|
}
|
|
15
|
-
return buildsResponse
|
|
20
|
+
return buildsResponse;
|
|
16
21
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
|
+
type RequestOptions = {
|
|
3
|
+
url: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
params?: Record<string, string | number>;
|
|
6
|
+
body?: any;
|
|
7
|
+
raise_error?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare class ApiResponse<T = any> {
|
|
10
|
+
private _response;
|
|
11
|
+
constructor(response: AxiosResponse<T>);
|
|
12
|
+
get data(): T;
|
|
13
|
+
get status(): number;
|
|
14
|
+
get statusText(): string;
|
|
15
|
+
get headers(): Record<string, string>;
|
|
16
|
+
get config(): AxiosRequestConfig;
|
|
17
|
+
get url(): string | undefined;
|
|
18
|
+
get ok(): boolean;
|
|
19
|
+
}
|
|
20
|
+
declare class ApiClient {
|
|
21
|
+
private instance;
|
|
22
|
+
private get axiosAgent();
|
|
23
|
+
private requestWrapper;
|
|
24
|
+
get<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
25
|
+
post<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
26
|
+
put<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
27
|
+
patch<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
28
|
+
delete<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
29
|
+
}
|
|
30
|
+
export declare const apiClient: ApiClient;
|
|
31
|
+
export type { ApiResponse, RequestOptions };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import httpsProxyAgentPkg from "https-proxy-agent";
|
|
3
|
+
const { HttpsProxyAgent } = httpsProxyAgentPkg;
|
|
4
|
+
import * as https from "https";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import config from "../config.js";
|
|
7
|
+
class ApiResponse {
|
|
8
|
+
_response;
|
|
9
|
+
constructor(response) {
|
|
10
|
+
this._response = response;
|
|
11
|
+
}
|
|
12
|
+
get data() {
|
|
13
|
+
return this._response.data;
|
|
14
|
+
}
|
|
15
|
+
get status() {
|
|
16
|
+
return this._response.status;
|
|
17
|
+
}
|
|
18
|
+
get statusText() {
|
|
19
|
+
return this._response.statusText;
|
|
20
|
+
}
|
|
21
|
+
get headers() {
|
|
22
|
+
const raw = this._response.headers;
|
|
23
|
+
const sanitized = {};
|
|
24
|
+
for (const key in raw) {
|
|
25
|
+
const value = raw[key];
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
sanitized[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return sanitized;
|
|
31
|
+
}
|
|
32
|
+
get config() {
|
|
33
|
+
return this._response.config;
|
|
34
|
+
}
|
|
35
|
+
get url() {
|
|
36
|
+
return this._response.config.url;
|
|
37
|
+
}
|
|
38
|
+
get ok() {
|
|
39
|
+
return this._response.status >= 200 && this._response.status < 300;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Utility to create HTTPS agent if needed (proxy/CA)
|
|
43
|
+
function getAxiosAgent() {
|
|
44
|
+
const proxyHost = config.browserstackLocalOptions.proxyHost;
|
|
45
|
+
const proxyPort = config.browserstackLocalOptions.proxyPort;
|
|
46
|
+
const caCertPath = config.browserstackLocalOptions.useCaCertificate;
|
|
47
|
+
// If both proxy host and port are defined
|
|
48
|
+
if (proxyHost && proxyPort) {
|
|
49
|
+
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
|
|
50
|
+
if (caCertPath && fs.existsSync(caCertPath)) {
|
|
51
|
+
// Proxy + CA cert
|
|
52
|
+
const ca = fs.readFileSync(caCertPath);
|
|
53
|
+
return new HttpsProxyAgent({
|
|
54
|
+
host: proxyHost,
|
|
55
|
+
port: Number(proxyPort),
|
|
56
|
+
ca,
|
|
57
|
+
rejectUnauthorized: false, // Set to true if you want strict SSL
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Proxy only
|
|
62
|
+
return new HttpsProxyAgent(proxyUrl);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (caCertPath && fs.existsSync(caCertPath)) {
|
|
66
|
+
// CA only
|
|
67
|
+
return new https.Agent({
|
|
68
|
+
ca: fs.readFileSync(caCertPath),
|
|
69
|
+
rejectUnauthorized: false, // Set to true for strict SSL
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Default agent (no proxy, no CA)
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
class ApiClient {
|
|
76
|
+
instance = axios.create();
|
|
77
|
+
get axiosAgent() {
|
|
78
|
+
return getAxiosAgent();
|
|
79
|
+
}
|
|
80
|
+
async requestWrapper(fn, raise_error = true) {
|
|
81
|
+
try {
|
|
82
|
+
const res = await fn(this.axiosAgent);
|
|
83
|
+
return new ApiResponse(res);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error.response && !raise_error) {
|
|
87
|
+
return new ApiResponse(error.response);
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async get({ url, headers, params, raise_error = true, }) {
|
|
93
|
+
return this.requestWrapper((agent) => this.instance.get(url, { headers, params, httpsAgent: agent }), raise_error);
|
|
94
|
+
}
|
|
95
|
+
async post({ url, headers, body, raise_error = true, }) {
|
|
96
|
+
return this.requestWrapper((agent) => this.instance.post(url, body, { headers, httpsAgent: agent }), raise_error);
|
|
97
|
+
}
|
|
98
|
+
async put({ url, headers, body, raise_error = true, }) {
|
|
99
|
+
return this.requestWrapper((agent) => this.instance.put(url, body, { headers, httpsAgent: agent }), raise_error);
|
|
100
|
+
}
|
|
101
|
+
async patch({ url, headers, body, raise_error = true, }) {
|
|
102
|
+
return this.requestWrapper((agent) => this.instance.patch(url, body, { headers, httpsAgent: agent }), raise_error);
|
|
103
|
+
}
|
|
104
|
+
async delete({ url, headers, params, raise_error = true, }) {
|
|
105
|
+
return this.requestWrapper((agent) => this.instance.delete(url, { headers, params, httpsAgent: agent }), raise_error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export const apiClient = new ApiClient();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const SessionType: {
|
|
2
|
+
readonly Automate: "automate";
|
|
3
|
+
readonly AppAutomate: "app-automate";
|
|
4
|
+
};
|
|
5
|
+
export declare const AutomateLogType: {
|
|
6
|
+
readonly NetworkLogs: "networkLogs";
|
|
7
|
+
readonly SessionLogs: "sessionLogs";
|
|
8
|
+
readonly ConsoleLogs: "consoleLogs";
|
|
9
|
+
};
|
|
10
|
+
export declare const AppAutomateLogType: {
|
|
11
|
+
readonly DeviceLogs: "deviceLogs";
|
|
12
|
+
readonly AppiumLogs: "appiumLogs";
|
|
13
|
+
readonly CrashLogs: "crashLogs";
|
|
14
|
+
};
|
|
15
|
+
export type SessionType = (typeof SessionType)[keyof typeof SessionType];
|
|
16
|
+
export type AutomateLogType = (typeof AutomateLogType)[keyof typeof AutomateLogType];
|
|
17
|
+
export type AppAutomateLogType = (typeof AppAutomateLogType)[keyof typeof AppAutomateLogType];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare enum BrowserStackProducts {
|
|
2
|
+
LIVE = "live",
|
|
3
|
+
APP_LIVE = "app_live",
|
|
4
|
+
APP_AUTOMATE = "app_automate"
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getDevicesAndBrowsers(type: BrowserStackProducts): Promise<any>;
|
package/dist/lib/device-cache.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { apiClient } from "./apiClient.js";
|
|
4
5
|
const CACHE_DIR = path.join(os.homedir(), ".browserstack", "combined_cache");
|
|
5
6
|
const CACHE_FILE = path.join(CACHE_DIR, "data.json");
|
|
6
7
|
const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
@@ -38,13 +39,12 @@ export async function getDevicesAndBrowsers(type) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
const liveRes = await
|
|
42
|
+
const liveRes = await apiClient.get({ url: URLS[type], raise_error: false });
|
|
42
43
|
if (!liveRes.ok) {
|
|
43
44
|
throw new Error(`Failed to fetch configuration from BrowserStack : ${type}=${liveRes.statusText}`);
|
|
44
45
|
}
|
|
45
|
-
const data = await liveRes.json();
|
|
46
46
|
cache = {
|
|
47
|
-
[type]: data,
|
|
47
|
+
[type]: liveRes.data,
|
|
48
48
|
};
|
|
49
49
|
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
|
|
50
50
|
return cache[type];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Formats an AxiosError into a CallToolResult with an appropriate message.
|
|
4
|
+
* @param err - The error object to format
|
|
5
|
+
* @param defaultText - The fallback error message
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatAxiosError(err: unknown, defaultText: string): CallToolResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function customFuzzySearch<T>(list: T[], keys: Array<keyof T | string>, query: string, limit?: number, maxDistance?: number): T[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function getBrowserStackAuth(config) {
|
|
2
|
+
const username = config["browserstack-username"];
|
|
3
|
+
const accessKey = config["browserstack-access-key"];
|
|
4
|
+
if (!username || !accessKey) {
|
|
5
|
+
throw new Error("BrowserStack credentials not set on server.authHeaders");
|
|
6
|
+
}
|
|
7
|
+
return `${username}:${accessKey}`;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const signedUrlMap: Map<string, object>;
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import logger from "../logger.js";
|
|
2
|
-
import
|
|
2
|
+
import { getBrowserStackAuth } from "./get-auth.js";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const packageJson = require("../../package.json");
|
|
6
6
|
import axios from "axios";
|
|
7
|
-
export function trackMCP(toolName, clientInfo, error) {
|
|
8
|
-
if (config.DEV_MODE) {
|
|
9
|
-
logger.info("Tracking MCP is disabled in dev mode");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
7
|
+
export function trackMCP(toolName, clientInfo, error, config) {
|
|
12
8
|
const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
|
|
13
9
|
const isSuccess = !error;
|
|
14
10
|
const mcpClient = clientInfo?.name || "unknown";
|
|
@@ -35,11 +31,16 @@ export function trackMCP(toolName, clientInfo, error) {
|
|
|
35
31
|
event.event_properties.error_type =
|
|
36
32
|
error instanceof Error ? error.constructor.name : "Unknown";
|
|
37
33
|
}
|
|
34
|
+
let authHeader = undefined;
|
|
35
|
+
if (config) {
|
|
36
|
+
const authString = getBrowserStackAuth(config);
|
|
37
|
+
authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;
|
|
38
|
+
}
|
|
38
39
|
axios
|
|
39
40
|
.post(instrumentationEndpoint, event, {
|
|
40
41
|
headers: {
|
|
41
42
|
"Content-Type": "application/json",
|
|
42
|
-
Authorization:
|
|
43
|
+
...(authHeader ? { Authorization: authHeader } : {}),
|
|
43
44
|
},
|
|
44
45
|
timeout: 2000,
|
|
45
46
|
})
|
package/dist/lib/local.js
CHANGED
|
@@ -65,7 +65,7 @@ export async function killExistingBrowserStackLocalProcesses() {
|
|
|
65
65
|
// Continue execution as there may not be any processes running
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
export async function ensureLocalBinarySetup(localIdentifier) {
|
|
68
|
+
export async function ensureLocalBinarySetup(username, password, localIdentifier) {
|
|
69
69
|
logger.info("Ensuring local binary setup as it is required for private URLs...");
|
|
70
70
|
if (config.USE_OWN_LOCAL_BINARY_PROCESS) {
|
|
71
71
|
logger.info("Using user's own BrowserStack Local binary process, checking if it's running...");
|
|
@@ -81,8 +81,8 @@ export async function ensureLocalBinarySetup(localIdentifier) {
|
|
|
81
81
|
// Use a single options object from config and extend with required fields
|
|
82
82
|
const bsLocalArgs = {
|
|
83
83
|
...(config.browserstackLocalOptions || {}),
|
|
84
|
-
key:
|
|
85
|
-
username
|
|
84
|
+
key: password,
|
|
85
|
+
username,
|
|
86
86
|
};
|
|
87
87
|
if (localIdentifier) {
|
|
88
88
|
bsLocalArgs.localIdentifier = localIdentifier;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiResponse } from "./apiClient.js";
|
|
2
|
+
export declare function sanitizeUrlParam(param: string): string;
|
|
3
|
+
export declare function maybeCompressBase64(base64: string): Promise<string>;
|
|
4
|
+
export declare function assertOkResponse(response: Response | ApiResponse, action: string): Promise<void>;
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { pino } from "pino";
|
|
2
|
-
|
|
2
|
+
// 1. The actual logger instance, swapped out as needed
|
|
3
|
+
let currentLogger;
|
|
3
4
|
if (process.env.NODE_ENV === "development") {
|
|
4
|
-
|
|
5
|
+
currentLogger = pino({
|
|
5
6
|
level: "debug",
|
|
6
7
|
transport: {
|
|
7
8
|
targets: [
|
|
@@ -21,8 +22,8 @@ if (process.env.NODE_ENV === "development") {
|
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
else {
|
|
24
|
-
//
|
|
25
|
-
|
|
25
|
+
// Null logger (logs go to /dev/null or NUL)
|
|
26
|
+
currentLogger = pino({
|
|
26
27
|
level: "info",
|
|
27
28
|
transport: {
|
|
28
29
|
target: "pino/file",
|
|
@@ -32,4 +33,19 @@ else {
|
|
|
32
33
|
},
|
|
33
34
|
});
|
|
34
35
|
}
|
|
36
|
+
// 2. Proxy logger: always delegates to the currentLogger
|
|
37
|
+
const logger = new Proxy({}, {
|
|
38
|
+
get(_target, prop) {
|
|
39
|
+
// Forward function calls to currentLogger
|
|
40
|
+
if (typeof currentLogger[prop] === "function") {
|
|
41
|
+
return (...args) => currentLogger[prop](...args);
|
|
42
|
+
}
|
|
43
|
+
// Forward property gets
|
|
44
|
+
return currentLogger[prop];
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
// 3. Setter to update the logger instance everywhere
|
|
48
|
+
export function setLogger(customLogger) {
|
|
49
|
+
currentLogger = customLogger;
|
|
50
|
+
}
|
|
35
51
|
export default logger;
|
package/dist/oninitialized.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import config from "./config.js";
|
|
2
1
|
import { trackMCP } from "./lib/instrumentation.js";
|
|
3
|
-
export function setupOnInitialized(server) {
|
|
2
|
+
export function setupOnInitialized(server, config) {
|
|
4
3
|
const nodeVersion = process.versions.node;
|
|
5
4
|
// Check for Node.js version
|
|
6
5
|
if (nodeVersion < "18.0.0") {
|
|
7
6
|
throw new Error("Node version is not supported. Please upgrade to 18.0.0 or later.");
|
|
8
7
|
}
|
|
9
|
-
// Check for BrowserStack credentials
|
|
10
|
-
if (!config.browserstackUsername || !config.browserstackAccessKey) {
|
|
11
|
-
throw new Error("BrowserStack credentials are missing. Please provide a valid username and access key.");
|
|
12
|
-
}
|
|
13
8
|
server.server.oninitialized = () => {
|
|
14
|
-
trackMCP("started", server.server.getClientVersion());
|
|
9
|
+
trackMCP("started", server.server.getClientVersion(), undefined, config);
|
|
15
10
|
};
|
|
16
11
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const packageJson = require("../package.json");
|
|
5
|
+
import logger from "./logger.js";
|
|
6
|
+
import addSDKTools from "./tools/bstack-sdk.js";
|
|
7
|
+
import addBrowserLiveTools from "./tools/live.js";
|
|
8
|
+
import addAccessibilityTools from "./tools/accessibility.js";
|
|
9
|
+
import addTestManagementTools from "./tools/testmanagement.js";
|
|
10
|
+
import addAppAutomationTools from "./tools/appautomate.js";
|
|
11
|
+
import addFailureLogsTools from "./tools/getFailureLogs.js";
|
|
12
|
+
import addAutomateTools from "./tools/automate.js";
|
|
13
|
+
import addSelfHealTools from "./tools/selfheal.js";
|
|
14
|
+
import addAppLiveTools from "./tools/applive.js";
|
|
15
|
+
import { setupOnInitialized } from "./oninitialized.js";
|
|
16
|
+
function registerTools(server, config) {
|
|
17
|
+
addAccessibilityTools(server, config);
|
|
18
|
+
addSDKTools(server, config);
|
|
19
|
+
addAppLiveTools(server, config);
|
|
20
|
+
addBrowserLiveTools(server, config);
|
|
21
|
+
addTestManagementTools(server, config);
|
|
22
|
+
addAppAutomationTools(server, config);
|
|
23
|
+
addFailureLogsTools(server, config);
|
|
24
|
+
addAutomateTools(server, config);
|
|
25
|
+
addSelfHealTools(server, config);
|
|
26
|
+
}
|
|
27
|
+
export function createMcpServer(config) {
|
|
28
|
+
logger.info("Creating BrowserStack MCP Server, version %s", packageJson.version);
|
|
29
|
+
// Create an MCP server
|
|
30
|
+
const server = new McpServer({
|
|
31
|
+
name: "BrowserStack MCP Server",
|
|
32
|
+
version: packageJson.version,
|
|
33
|
+
});
|
|
34
|
+
setupOnInitialized(server, config);
|
|
35
|
+
registerTools(server, config);
|
|
36
|
+
return server;
|
|
37
|
+
}
|
|
@@ -4,9 +4,13 @@ import { AccessibilityReportFetcher } from "./accessiblity-utils/report-fetcher.
|
|
|
4
4
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
5
|
import { parseAccessibilityReportFromCSV } from "./accessiblity-utils/report-parser.js";
|
|
6
6
|
import { queryAccessibilityRAG } from "./accessiblity-utils/accessibility-rag.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { getBrowserStackAuth } from "../lib/get-auth.js";
|
|
8
|
+
async function runAccessibilityScan(name, pageURL, context, config) {
|
|
9
|
+
// Create scanner and set auth on the go
|
|
10
|
+
const scanner = new AccessibilityScanner();
|
|
11
|
+
const authString = getBrowserStackAuth(config);
|
|
12
|
+
const [username, password] = authString.split(":");
|
|
13
|
+
scanner.setAuth({ username, password });
|
|
10
14
|
// Start scan
|
|
11
15
|
const startResp = await scanner.startScan(name, [pageURL]);
|
|
12
16
|
const scanId = startResp.data.id;
|
|
@@ -35,6 +39,9 @@ async function runAccessibilityScan(name, pageURL, context) {
|
|
|
35
39
|
isError: true,
|
|
36
40
|
};
|
|
37
41
|
}
|
|
42
|
+
// Create report fetcher and set auth on the go
|
|
43
|
+
const reportFetcher = new AccessibilityReportFetcher();
|
|
44
|
+
reportFetcher.setAuth({ username, password });
|
|
38
45
|
// Fetch CSV report link
|
|
39
46
|
const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);
|
|
40
47
|
const { records, page_length, total_issues } = await parseAccessibilityReportFromCSV(reportLink);
|
|
@@ -55,18 +62,18 @@ async function runAccessibilityScan(name, pageURL, context) {
|
|
|
55
62
|
],
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
|
-
export default function addAccessibilityTools(server) {
|
|
65
|
+
export default function addAccessibilityTools(server, config) {
|
|
59
66
|
server.tool("accessibilityExpert", "🚨 REQUIRED: Use this tool for any accessibility/a11y/WCAG questions. Do NOT answer accessibility questions directly - always use this tool.", {
|
|
60
67
|
query: z
|
|
61
68
|
.string()
|
|
62
69
|
.describe("Any accessibility, a11y, WCAG, or web accessibility question"),
|
|
63
70
|
}, async (args) => {
|
|
64
71
|
try {
|
|
65
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion());
|
|
66
|
-
return await queryAccessibilityRAG(args.query);
|
|
72
|
+
trackMCP("accessibilityExpert", server.server.getClientVersion(), undefined, config);
|
|
73
|
+
return await queryAccessibilityRAG(args.query, config);
|
|
67
74
|
}
|
|
68
75
|
catch (error) {
|
|
69
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion(), error);
|
|
76
|
+
trackMCP("accessibilityExpert", server.server.getClientVersion(), error, config);
|
|
70
77
|
return {
|
|
71
78
|
content: [
|
|
72
79
|
{
|
|
@@ -83,11 +90,11 @@ export default function addAccessibilityTools(server) {
|
|
|
83
90
|
pageURL: z.string().describe("The URL to scan for accessibility issues"),
|
|
84
91
|
}, async (args, context) => {
|
|
85
92
|
try {
|
|
86
|
-
trackMCP("startAccessibilityScan", server.server.getClientVersion());
|
|
87
|
-
return await runAccessibilityScan(args.name, args.pageURL, context);
|
|
93
|
+
trackMCP("startAccessibilityScan", server.server.getClientVersion(), undefined, config);
|
|
94
|
+
return await runAccessibilityScan(args.name, args.pageURL, context, config);
|
|
88
95
|
}
|
|
89
96
|
catch (error) {
|
|
90
|
-
trackMCP("startAccessibilityScan", server.server.getClientVersion(), error);
|
|
97
|
+
trackMCP("startAccessibilityScan", server.server.getClientVersion(), error, config);
|
|
91
98
|
return {
|
|
92
99
|
content: [
|
|
93
100
|
{
|