@browserstack/mcp-server 1.1.9 → 1.2.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/README.md +4 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.js +4 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -31
- 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 +25 -0
- package/dist/server-factory.js +70 -0
- package/dist/tools/accessibility.d.ts +3 -0
- package/dist/tools/accessibility.js +21 -12
- 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 +24 -21
- 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 +9 -7
- 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 +9 -7
- package/dist/tools/bstack-sdk.d.ts +17 -0
- package/dist/tools/bstack-sdk.js +15 -8
- 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 +15 -16
- 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 +28 -16
- 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 +9 -7
- 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 +53 -53
- package/package.json +1 -1
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
2
2
|
import { filterLinesByKeywords, validateLogResponse, } from "./utils.js";
|
|
3
|
-
|
|
3
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
4
4
|
// NETWORK LOGS
|
|
5
|
-
export async function retrieveNetworkFailures(sessionId) {
|
|
5
|
+
export async function retrieveNetworkFailures(sessionId, config) {
|
|
6
6
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const authString = getBrowserStackAuth(config);
|
|
8
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
9
|
+
const response = await apiClient.get({
|
|
10
|
+
url,
|
|
9
11
|
headers: {
|
|
10
12
|
"Content-Type": "application/json",
|
|
11
13
|
Authorization: `Basic ${auth}`,
|
|
12
14
|
},
|
|
15
|
+
raise_error: false,
|
|
13
16
|
});
|
|
14
17
|
const validationError = validateLogResponse(response, "network logs");
|
|
15
18
|
if (validationError)
|
|
16
19
|
return validationError.message;
|
|
17
|
-
const networklogs =
|
|
20
|
+
const networklogs = response.data;
|
|
18
21
|
const failureEntries = networklogs.log.entries.filter((entry) => entry.response.status === 0 ||
|
|
19
22
|
entry.response.status >= 400 ||
|
|
20
23
|
entry.response._error !== undefined);
|
|
@@ -37,36 +40,48 @@ export async function retrieveNetworkFailures(sessionId) {
|
|
|
37
40
|
: "No network failures found";
|
|
38
41
|
}
|
|
39
42
|
// SESSION LOGS
|
|
40
|
-
export async function retrieveSessionFailures(sessionId) {
|
|
43
|
+
export async function retrieveSessionFailures(sessionId, config) {
|
|
41
44
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
|
|
42
|
-
const
|
|
45
|
+
const authString = getBrowserStackAuth(config);
|
|
46
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
47
|
+
const response = await apiClient.get({
|
|
48
|
+
url,
|
|
43
49
|
headers: {
|
|
44
50
|
"Content-Type": "application/json",
|
|
45
51
|
Authorization: `Basic ${auth}`,
|
|
46
52
|
},
|
|
53
|
+
raise_error: false,
|
|
47
54
|
});
|
|
48
55
|
const validationError = validateLogResponse(response, "session logs");
|
|
49
56
|
if (validationError)
|
|
50
57
|
return validationError.message;
|
|
51
|
-
const logText =
|
|
58
|
+
const logText = typeof response.data === "string"
|
|
59
|
+
? response.data
|
|
60
|
+
: JSON.stringify(response.data);
|
|
52
61
|
const logs = filterSessionFailures(logText);
|
|
53
62
|
return logs.length > 0
|
|
54
63
|
? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
55
64
|
: "No session failures found";
|
|
56
65
|
}
|
|
57
66
|
// CONSOLE LOGS
|
|
58
|
-
export async function retrieveConsoleFailures(sessionId) {
|
|
67
|
+
export async function retrieveConsoleFailures(sessionId, config) {
|
|
59
68
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
|
|
60
|
-
const
|
|
69
|
+
const authString = getBrowserStackAuth(config);
|
|
70
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
71
|
+
const response = await apiClient.get({
|
|
72
|
+
url,
|
|
61
73
|
headers: {
|
|
62
74
|
"Content-Type": "application/json",
|
|
63
75
|
Authorization: `Basic ${auth}`,
|
|
64
76
|
},
|
|
77
|
+
raise_error: false,
|
|
65
78
|
});
|
|
66
79
|
const validationError = validateLogResponse(response, "console logs");
|
|
67
80
|
if (validationError)
|
|
68
81
|
return validationError.message;
|
|
69
|
-
const logText =
|
|
82
|
+
const logText = typeof response.data === "string"
|
|
83
|
+
? response.data
|
|
84
|
+
: JSON.stringify(response.data);
|
|
70
85
|
const logs = filterConsoleFailures(logText);
|
|
71
86
|
return logs.length > 0
|
|
72
87
|
? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiResponse } from "../../lib/apiClient.js";
|
|
2
|
+
export interface LogResponse {
|
|
3
|
+
logs?: any[];
|
|
4
|
+
message?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HarFile {
|
|
7
|
+
log: {
|
|
8
|
+
entries: HarEntry[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface HarEntry {
|
|
12
|
+
startedDateTime: string;
|
|
13
|
+
request: {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
queryString?: {
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
response: {
|
|
22
|
+
status: number;
|
|
23
|
+
statusText?: string;
|
|
24
|
+
_error?: string;
|
|
25
|
+
};
|
|
26
|
+
serverIPAddress?: string;
|
|
27
|
+
time?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function validateLogResponse(response: Response | ApiResponse, logType: string): LogResponse | null;
|
|
30
|
+
export declare function filterLinesByKeywords(logText: string, keywords: string[]): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
4
|
+
import { AppAutomateLogType, AutomateLogType, SessionType } from "../lib/constants.js";
|
|
5
|
+
type LogType = AutomateLogType | AppAutomateLogType;
|
|
6
|
+
type SessionTypeValues = SessionType;
|
|
7
|
+
export declare function getFailureLogs(args: {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
buildId?: string;
|
|
10
|
+
logTypes: LogType[];
|
|
11
|
+
sessionType: SessionTypeValues;
|
|
12
|
+
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
13
|
+
export default function registerGetFailureLogs(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
|
14
|
+
export {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import logger from "../logger.js";
|
|
3
2
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
4
3
|
import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
|
|
5
4
|
import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
|
|
6
5
|
import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
|
|
7
6
|
// Main log fetcher function
|
|
8
|
-
export async function getFailureLogs(args) {
|
|
7
|
+
export async function getFailureLogs(args, config) {
|
|
9
8
|
const results = [];
|
|
10
9
|
const errors = [];
|
|
11
10
|
let validLogTypes = [];
|
|
@@ -53,32 +52,32 @@ export async function getFailureLogs(args) {
|
|
|
53
52
|
for (const logType of validLogTypes) {
|
|
54
53
|
switch (logType) {
|
|
55
54
|
case AutomateLogType.NetworkLogs: {
|
|
56
|
-
response = await retrieveNetworkFailures(args.sessionId);
|
|
55
|
+
response = await retrieveNetworkFailures(args.sessionId, config);
|
|
57
56
|
results.push({ type: "text", text: response });
|
|
58
57
|
break;
|
|
59
58
|
}
|
|
60
59
|
case AutomateLogType.SessionLogs: {
|
|
61
|
-
response = await retrieveSessionFailures(args.sessionId);
|
|
60
|
+
response = await retrieveSessionFailures(args.sessionId, config);
|
|
62
61
|
results.push({ type: "text", text: response });
|
|
63
62
|
break;
|
|
64
63
|
}
|
|
65
64
|
case AutomateLogType.ConsoleLogs: {
|
|
66
|
-
response = await retrieveConsoleFailures(args.sessionId);
|
|
65
|
+
response = await retrieveConsoleFailures(args.sessionId, config);
|
|
67
66
|
results.push({ type: "text", text: response });
|
|
68
67
|
break;
|
|
69
68
|
}
|
|
70
69
|
case AppAutomateLogType.DeviceLogs: {
|
|
71
|
-
response = await retrieveDeviceLogs(args.sessionId, args.buildId);
|
|
70
|
+
response = await retrieveDeviceLogs(args.sessionId, args.buildId, config);
|
|
72
71
|
results.push({ type: "text", text: response });
|
|
73
72
|
break;
|
|
74
73
|
}
|
|
75
74
|
case AppAutomateLogType.AppiumLogs: {
|
|
76
|
-
response = await retrieveAppiumLogs(args.sessionId, args.buildId);
|
|
75
|
+
response = await retrieveAppiumLogs(args.sessionId, args.buildId, config);
|
|
77
76
|
results.push({ type: "text", text: response });
|
|
78
77
|
break;
|
|
79
78
|
}
|
|
80
79
|
case AppAutomateLogType.CrashLogs: {
|
|
81
|
-
response = await retrieveCrashLogs(args.sessionId, args.buildId);
|
|
80
|
+
response = await retrieveCrashLogs(args.sessionId, args.buildId, config);
|
|
82
81
|
results.push({ type: "text", text: response });
|
|
83
82
|
break;
|
|
84
83
|
}
|
|
@@ -98,8 +97,9 @@ export async function getFailureLogs(args) {
|
|
|
98
97
|
return { content: results };
|
|
99
98
|
}
|
|
100
99
|
// Register tool with the MCP server
|
|
101
|
-
export default function registerGetFailureLogs(server) {
|
|
102
|
-
|
|
100
|
+
export default function registerGetFailureLogs(server, config) {
|
|
101
|
+
const tools = {};
|
|
102
|
+
tools.getFailureLogs = server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
|
|
103
103
|
sessionType: z
|
|
104
104
|
.enum([SessionType.Automate, SessionType.AppAutomate])
|
|
105
105
|
.describe("Type of BrowserStack session. Must be explicitly provided by the user."),
|
|
@@ -122,18 +122,16 @@ export default function registerGetFailureLogs(server) {
|
|
|
122
122
|
.describe("The types of logs to fetch."),
|
|
123
123
|
}, async (args) => {
|
|
124
124
|
try {
|
|
125
|
-
trackMCP("getFailureLogs", server.server.getClientVersion());
|
|
126
|
-
return await getFailureLogs(args);
|
|
125
|
+
trackMCP("getFailureLogs", server.server.getClientVersion(), undefined, config);
|
|
126
|
+
return await getFailureLogs(args, config);
|
|
127
127
|
}
|
|
128
128
|
catch (error) {
|
|
129
|
-
|
|
130
|
-
trackMCP("getFailureLogs", server.server.getClientVersion(), error);
|
|
131
|
-
logger.error("Failed to fetch logs: %s", message);
|
|
129
|
+
trackMCP("getFailureLogs", server.server.getClientVersion(), error, config);
|
|
132
130
|
return {
|
|
133
131
|
content: [
|
|
134
132
|
{
|
|
135
133
|
type: "text",
|
|
136
|
-
text: `Failed to fetch logs: ${message}`,
|
|
134
|
+
text: `Failed to fetch failure logs: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
137
135
|
isError: true,
|
|
138
136
|
},
|
|
139
137
|
],
|
|
@@ -141,4 +139,5 @@ export default function registerGetFailureLogs(server) {
|
|
|
141
139
|
};
|
|
142
140
|
}
|
|
143
141
|
});
|
|
142
|
+
return tools;
|
|
144
143
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DesktopSearchArgs, MobileSearchArgs } from "./types.js";
|
|
2
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Entrypoint: detects platformType & delegates.
|
|
5
|
+
*/
|
|
6
|
+
export declare function startBrowserSession(args: DesktopSearchArgs | MobileSearchArgs, config: BrowserStackConfig): Promise<string>;
|
|
@@ -4,13 +4,18 @@ import { filterDesktop } from "./desktop-filter.js";
|
|
|
4
4
|
import { filterMobile } from "./mobile-filter.js";
|
|
5
5
|
import { PlatformType, } from "./types.js";
|
|
6
6
|
import { isLocalURL, ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js";
|
|
7
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
8
|
+
import envConfig from "../../config.js";
|
|
7
9
|
/**
|
|
8
10
|
* Prepares local tunnel setup based on URL type
|
|
9
11
|
*/
|
|
10
|
-
async function prepareLocalTunnel(url) {
|
|
12
|
+
async function prepareLocalTunnel(url, username, password) {
|
|
11
13
|
const isLocal = isLocalURL(url);
|
|
14
|
+
if (isLocal && envConfig.REMOTE_MCP) {
|
|
15
|
+
throw new Error("Local URLs are not supported in this remote mcp. Please use a public URL.");
|
|
16
|
+
}
|
|
12
17
|
if (isLocal) {
|
|
13
|
-
await ensureLocalBinarySetup();
|
|
18
|
+
await ensureLocalBinarySetup(username, password);
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
21
|
await killExistingBrowserStackLocalProcesses();
|
|
@@ -20,15 +25,23 @@ async function prepareLocalTunnel(url) {
|
|
|
20
25
|
/**
|
|
21
26
|
* Entrypoint: detects platformType & delegates.
|
|
22
27
|
*/
|
|
23
|
-
export async function startBrowserSession(args) {
|
|
28
|
+
export async function startBrowserSession(args, config) {
|
|
24
29
|
const entry = args.platformType === PlatformType.DESKTOP
|
|
25
30
|
? await filterDesktop(args)
|
|
26
31
|
: await filterMobile(args);
|
|
27
|
-
|
|
32
|
+
// Get credentials from config
|
|
33
|
+
const authString = getBrowserStackAuth(config);
|
|
34
|
+
const [username, password] = authString.split(":");
|
|
35
|
+
if (!username || !password) {
|
|
36
|
+
throw new Error("BrowserStack credentials are not set. Please configure them in the server settings.");
|
|
37
|
+
}
|
|
38
|
+
const isLocal = await prepareLocalTunnel(args.url, username, password);
|
|
28
39
|
const url = args.platformType === PlatformType.DESKTOP
|
|
29
40
|
? buildDesktopUrl(args, entry, isLocal)
|
|
30
41
|
: buildMobileUrl(args, entry, isLocal);
|
|
31
|
-
|
|
42
|
+
if (!envConfig.REMOTE_MCP) {
|
|
43
|
+
openBrowser(url);
|
|
44
|
+
}
|
|
32
45
|
return entry.notes ? `${url}, ${entry.notes}` : url;
|
|
33
46
|
}
|
|
34
47
|
function buildDesktopUrl(args, e, isLocal) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface DesktopSearchArgs {
|
|
2
|
+
platformType: "desktop";
|
|
3
|
+
url: string;
|
|
4
|
+
os: string;
|
|
5
|
+
osVersion: string;
|
|
6
|
+
browser: string;
|
|
7
|
+
browserVersion: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DesktopEntry {
|
|
10
|
+
os: string;
|
|
11
|
+
os_version: string;
|
|
12
|
+
browser: string;
|
|
13
|
+
browser_version: string;
|
|
14
|
+
notes?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MobileSearchArgs {
|
|
17
|
+
platformType: "mobile";
|
|
18
|
+
url: string;
|
|
19
|
+
os: string;
|
|
20
|
+
osVersion: string;
|
|
21
|
+
device: string;
|
|
22
|
+
browser: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MobileEntry {
|
|
25
|
+
os: string;
|
|
26
|
+
os_version: string;
|
|
27
|
+
display_name: string;
|
|
28
|
+
notes?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare enum PlatformType {
|
|
31
|
+
DESKTOP = "desktop",
|
|
32
|
+
MOBILE = "mobile"
|
|
33
|
+
}
|
package/dist/tools/live.js
CHANGED
|
@@ -3,6 +3,7 @@ import logger from "../logger.js";
|
|
|
3
3
|
import { startBrowserSession } from "./live-utils/start-session.js";
|
|
4
4
|
import { PlatformType } from "./live-utils/types.js";
|
|
5
5
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
6
|
+
import globalConfig from "../config.js";
|
|
6
7
|
// Define the schema shape
|
|
7
8
|
const LiveArgsShape = {
|
|
8
9
|
platformType: z
|
|
@@ -28,7 +29,7 @@ const LiveArgsSchema = z.object(LiveArgsShape);
|
|
|
28
29
|
/**
|
|
29
30
|
* Launches a desktop browser session
|
|
30
31
|
*/
|
|
31
|
-
async function launchDesktopSession(args) {
|
|
32
|
+
async function launchDesktopSession(args, config) {
|
|
32
33
|
if (!args.desiredBrowser)
|
|
33
34
|
throw new Error("You must provide a desiredBrowser");
|
|
34
35
|
if (!args.desiredBrowserVersion)
|
|
@@ -40,12 +41,12 @@ async function launchDesktopSession(args) {
|
|
|
40
41
|
osVersion: args.desiredOSVersion,
|
|
41
42
|
browser: args.desiredBrowser,
|
|
42
43
|
browserVersion: args.desiredBrowserVersion,
|
|
43
|
-
});
|
|
44
|
+
}, config);
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* Launches a mobile browser session
|
|
47
48
|
*/
|
|
48
|
-
async function launchMobileSession(args) {
|
|
49
|
+
async function launchMobileSession(args, config) {
|
|
49
50
|
if (!args.desiredDevice)
|
|
50
51
|
throw new Error("You must provide a desiredDevice");
|
|
51
52
|
return startBrowserSession({
|
|
@@ -55,36 +56,46 @@ async function launchMobileSession(args) {
|
|
|
55
56
|
os: args.desiredOS,
|
|
56
57
|
osVersion: args.desiredOSVersion,
|
|
57
58
|
device: args.desiredDevice,
|
|
58
|
-
});
|
|
59
|
+
}, config);
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
62
|
* Handles the core logic for running a browser session
|
|
62
63
|
*/
|
|
63
|
-
async function runBrowserSession(rawArgs) {
|
|
64
|
+
async function runBrowserSession(rawArgs, config) {
|
|
64
65
|
// Validate and narrow
|
|
65
66
|
const args = LiveArgsSchema.parse(rawArgs);
|
|
66
67
|
// Branch desktop vs mobile and delegate
|
|
67
68
|
const launchUrl = args.platformType === PlatformType.DESKTOP
|
|
68
|
-
? await launchDesktopSession(args)
|
|
69
|
-
: await launchMobileSession(args);
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
? await launchDesktopSession(args, config)
|
|
70
|
+
: await launchMobileSession(args, config);
|
|
71
|
+
let response = [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
if (globalConfig.REMOTE_MCP) {
|
|
78
|
+
response = [
|
|
72
79
|
{
|
|
73
80
|
type: "text",
|
|
74
|
-
text: `✅
|
|
81
|
+
text: `✅ To start the session. Click on ${launchUrl}`,
|
|
75
82
|
},
|
|
76
|
-
]
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: response,
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
|
-
export default function addBrowserLiveTools(server) {
|
|
80
|
-
|
|
89
|
+
export default function addBrowserLiveTools(server, config) {
|
|
90
|
+
const tools = {};
|
|
91
|
+
tools.runBrowserLiveSession = server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
|
|
81
92
|
try {
|
|
82
|
-
trackMCP("runBrowserLiveSession", server.server.getClientVersion());
|
|
83
|
-
return await runBrowserSession(args);
|
|
93
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), undefined, config);
|
|
94
|
+
return await runBrowserSession(args, config);
|
|
84
95
|
}
|
|
85
96
|
catch (error) {
|
|
86
97
|
logger.error("Live session failed: %s", error);
|
|
87
|
-
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error);
|
|
98
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error, config);
|
|
88
99
|
return {
|
|
89
100
|
content: [
|
|
90
101
|
{
|
|
@@ -97,4 +108,5 @@ export default function addBrowserLiveTools(server) {
|
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
110
|
});
|
|
111
|
+
return tools;
|
|
100
112
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
4
|
+
export declare function getFailuresInLastRun(buildName: string, projectName: string, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
5
|
+
export default function addObservabilityTools(server: McpServer, config: BrowserStackConfig): void;
|
|
@@ -2,19 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import { getLatestO11YBuildInfo } from "../lib/api.js";
|
|
3
3
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
4
4
|
import logger from "../logger.js";
|
|
5
|
-
export async function getFailuresInLastRun(buildName, projectName) {
|
|
6
|
-
const buildsData = await getLatestO11YBuildInfo(buildName, projectName);
|
|
7
|
-
|
|
5
|
+
export async function getFailuresInLastRun(buildName, projectName, config) {
|
|
6
|
+
const buildsData = await getLatestO11YBuildInfo(buildName, projectName, config);
|
|
7
|
+
if (!buildsData.data) {
|
|
8
|
+
throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
|
|
9
|
+
}
|
|
10
|
+
const observabilityUrl = buildsData.data.observability_url;
|
|
8
11
|
if (!observabilityUrl) {
|
|
9
12
|
throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
|
|
10
13
|
}
|
|
11
14
|
let overview = "No overview available";
|
|
12
|
-
if (buildsData.unique_errors?.overview?.insight) {
|
|
13
|
-
overview = buildsData.unique_errors.overview.insight;
|
|
15
|
+
if (buildsData.data.unique_errors?.overview?.insight) {
|
|
16
|
+
overview = buildsData.data.unique_errors.overview.insight;
|
|
14
17
|
}
|
|
15
18
|
let details = "No error details available";
|
|
16
|
-
if (buildsData.unique_errors?.top_unique_errors?.length > 0) {
|
|
17
|
-
details = buildsData.unique_errors.top_unique_errors
|
|
19
|
+
if (buildsData.data.unique_errors?.top_unique_errors?.length > 0) {
|
|
20
|
+
details = buildsData.data.unique_errors.top_unique_errors
|
|
18
21
|
.map((error) => error.error)
|
|
19
22
|
.filter(Boolean)
|
|
20
23
|
.join("\n");
|
|
@@ -28,7 +31,7 @@ export async function getFailuresInLastRun(buildName, projectName) {
|
|
|
28
31
|
],
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
|
-
export default function addObservabilityTools(server) {
|
|
34
|
+
export default function addObservabilityTools(server, config) {
|
|
32
35
|
server.tool("getFailuresInLastRun", "Use this tool to debug failures in the last run of the test suite on BrowserStack. Use only when browserstack.yml file is present in the project root.", {
|
|
33
36
|
buildName: z
|
|
34
37
|
.string()
|
|
@@ -38,12 +41,12 @@ export default function addObservabilityTools(server) {
|
|
|
38
41
|
.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."),
|
|
39
42
|
}, async (args) => {
|
|
40
43
|
try {
|
|
41
|
-
trackMCP("getFailuresInLastRun", server.server.getClientVersion());
|
|
42
|
-
return await getFailuresInLastRun(args.buildName, args.projectName);
|
|
44
|
+
trackMCP("getFailuresInLastRun", server.server.getClientVersion(), undefined, config);
|
|
45
|
+
return await getFailuresInLastRun(args.buildName, args.projectName, config);
|
|
43
46
|
}
|
|
44
47
|
catch (error) {
|
|
45
48
|
logger.error("Failed to get failures in the last run: %s", error);
|
|
46
|
-
trackMCP("getFailuresInLastRun", server.server.getClientVersion(), error);
|
|
49
|
+
trackMCP("getFailuresInLastRun", server.server.getClientVersion(), error, config);
|
|
47
50
|
return {
|
|
48
51
|
content: [
|
|
49
52
|
{
|
|
@@ -4,6 +4,7 @@ const JAVA_FRAMEWORK_MAP = {
|
|
|
4
4
|
junit5: "junit5",
|
|
5
5
|
junit4: "junit4",
|
|
6
6
|
cucumber: "cucumber-testng",
|
|
7
|
+
serenity: "serenity",
|
|
7
8
|
};
|
|
8
9
|
// Common Gradle setup instructions (platform-independent)
|
|
9
10
|
const GRADLE_SETUP_INSTRUCTIONS = `
|
|
@@ -17,7 +18,7 @@ const GRADLE_SETUP_INSTRUCTIONS = `
|
|
|
17
18
|
3. Add javaagent to gradle tasks:
|
|
18
19
|
jvmArgs "-javaagent:\${browserstackSDKArtifact.file}"
|
|
19
20
|
`;
|
|
20
|
-
export function getSDKPrefixCommand(language, framework) {
|
|
21
|
+
export function getSDKPrefixCommand(language, framework, username, accessKey) {
|
|
21
22
|
switch (language) {
|
|
22
23
|
case "nodejs":
|
|
23
24
|
return `---STEP---
|
|
@@ -28,7 +29,7 @@ npm i -D browserstack-node-sdk@latest
|
|
|
28
29
|
---STEP---
|
|
29
30
|
Run the following command to setup browserstack sdk:
|
|
30
31
|
\`\`\`bash
|
|
31
|
-
npx setup --username ${
|
|
32
|
+
npx setup --username ${username} --key ${accessKey}
|
|
32
33
|
\`\`\`
|
|
33
34
|
---STEP---
|
|
34
35
|
Edit the browserstack.yml file that was created in the project root to add your desired platforms and browsers.`;
|
|
@@ -40,8 +41,8 @@ Edit the browserstack.yml file that was created in the project root to add your
|
|
|
40
41
|
: `mvn archetype:generate -B -DarchetypeGroupId=com.browserstack \\
|
|
41
42
|
-DarchetypeArtifactId=browserstack-sdk-archetype-integrate -DarchetypeVersion=1.0 \\
|
|
42
43
|
-DgroupId=com.browserstack -DartifactId=browserstack-sdk-archetype-integrate -Dversion=1.0 \\
|
|
43
|
-
-DBROWSERSTACK_USERNAME="${
|
|
44
|
-
-DBROWSERSTACK_ACCESS_KEY="${
|
|
44
|
+
-DBROWSERSTACK_USERNAME="${username}" \\
|
|
45
|
+
-DBROWSERSTACK_ACCESS_KEY="${accessKey}" \\
|
|
45
46
|
-DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`;
|
|
46
47
|
const platformLabel = isWindows ? "Windows" : "macOS/Linux";
|
|
47
48
|
return `---STEP---
|