@browserstack/mcp-server 1.2.17-beta.1 → 1.2.18

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,60 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ export interface TestCodeEntry {
3
+ testRunId: number;
4
+ code: string | null;
5
+ filename: string | null;
6
+ url: string | null;
7
+ language?: string | null;
8
+ type?: string | null;
9
+ }
10
+ /**
11
+ * Why a session's test code fetch succeeded or failed, so downstream callers
12
+ * can phrase their "please give me the file" prompt correctly:
13
+ * - ok: entries returned with real `code`/`filename`.
14
+ * - non_sdk_build: HTTP 200, entries present but every entry has
15
+ * `code === null && filename === null`. Classic signature
16
+ * of a non-SDK Automate build — the source is not
17
+ * introspectable via the API, so ask the user for the
18
+ * file path.
19
+ * - empty: HTTP 200 with no entries — session had no tests.
20
+ * - unauthorized: 401 — credentials are wrong/expired. Offer retry.
21
+ * - forbidden: 403 — credentials valid but no access to this session.
22
+ * - not_found: 404 — session id is wrong.
23
+ * - error: any other transport/network failure.
24
+ */
25
+ export type TestCodeFetchStatus = "ok" | "non_sdk_build" | "empty" | "unauthorized" | "forbidden" | "not_found" | "error";
26
+ export interface SessionTestCode {
27
+ sessionId: string;
28
+ tests: TestCodeEntry[];
29
+ status: TestCodeFetchStatus;
30
+ httpStatus?: number;
31
+ errorMessage?: string;
32
+ }
33
+ /**
34
+ * Fetches the test code for all tests in a given session from the
35
+ * Observability API.
36
+ *
37
+ * Endpoint: GET /ext/v1/sessions/{sessionId}/testCode
38
+ * Returns: Array of { testRunId, code, filename, url }
39
+ */
40
+ export declare function fetchTestCodeBySession(sessionId: string, config: BrowserStackConfig): Promise<SessionTestCode>;
41
+ /**
42
+ * Builds a directive guidance block for the calling LLM when one or more
43
+ * sessions did not return usable test code. Each status block contains:
44
+ * - A diagnosis (what happened, per BrowserStack)
45
+ * - An explicit "do NOT paraphrase this as X" constraint
46
+ * - A ready-to-say phrasing the LLM can quote when replying to the user
47
+ *
48
+ * Returns empty string when every session returned `status: ok`.
49
+ */
50
+ export declare function describeTestCodeFetchIssues(sessionTestCodes: SessionTestCode[]): string;
51
+ /**
52
+ * Fetches test code for multiple sessions in parallel.
53
+ * Failures for individual sessions are logged and skipped (partial results).
54
+ */
55
+ export declare function fetchTestCodeForSessions(sessionIds: string[], config: BrowserStackConfig): Promise<SessionTestCode[]>;
56
+ /**
57
+ * Formats test code entries into a concise context string suitable for
58
+ * inclusion in an LLM prompt. Groups by file, includes code snippets.
59
+ */
60
+ export declare function formatTestCodeAsContext(sessionTestCodes: SessionTestCode[]): string;
@@ -0,0 +1,258 @@
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
+ import { apiClient } from "../../lib/apiClient.js";
3
+ import logger from "../../logger.js";
4
+ const OBSERVABILITY_API_BASE = "https://api-automation.browserstack.com/ext/v1";
5
+ /**
6
+ * Normalizes the testCode response. The Observability API used to return a
7
+ * bare array of TestCodeEntry, but now wraps it as
8
+ * `{ sessionId, buildUuid, tests: TestCodeEntry[] }`. Accept both shapes so
9
+ * the integration is resilient to either deployment.
10
+ */
11
+ function extractTests(data) {
12
+ if (!data)
13
+ return [];
14
+ if (Array.isArray(data))
15
+ return data;
16
+ return Array.isArray(data.tests) ? data.tests : [];
17
+ }
18
+ function classifyByHttpStatus(status) {
19
+ if (status === 401)
20
+ return "unauthorized";
21
+ if (status === 403)
22
+ return "forbidden";
23
+ if (status === 404)
24
+ return "not_found";
25
+ return "error";
26
+ }
27
+ function classifyOkResponse(tests) {
28
+ if (tests.length === 0)
29
+ return "empty";
30
+ const allNullCode = tests.every((t) => t.code === null && t.filename === null);
31
+ return allNullCode ? "non_sdk_build" : "ok";
32
+ }
33
+ /**
34
+ * Fetches the test code for all tests in a given session from the
35
+ * Observability API.
36
+ *
37
+ * Endpoint: GET /ext/v1/sessions/{sessionId}/testCode
38
+ * Returns: Array of { testRunId, code, filename, url }
39
+ */
40
+ export async function fetchTestCodeBySession(sessionId, config) {
41
+ if (!sessionId) {
42
+ throw new Error("sessionId is required to fetch test code");
43
+ }
44
+ const authString = getBrowserStackAuth(config);
45
+ const auth = Buffer.from(authString).toString("base64");
46
+ const url = `${OBSERVABILITY_API_BASE}/sessions/${encodeURIComponent(sessionId)}/testCode`;
47
+ try {
48
+ const response = await apiClient.get({
49
+ url,
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ Authorization: `Basic ${auth}`,
53
+ },
54
+ raise_error: false,
55
+ });
56
+ if (!response.ok) {
57
+ logger.warn(`Failed to fetch test code for session ${sessionId}: HTTP ${response.status}`);
58
+ return {
59
+ sessionId,
60
+ tests: [],
61
+ status: classifyByHttpStatus(response.status),
62
+ httpStatus: response.status,
63
+ };
64
+ }
65
+ const tests = extractTests(response.data);
66
+ return {
67
+ sessionId,
68
+ tests,
69
+ status: classifyOkResponse(tests),
70
+ httpStatus: response.status,
71
+ };
72
+ }
73
+ catch (error) {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ logger.warn(`Error fetching test code for session ${sessionId}: ${message}`);
76
+ return {
77
+ sessionId,
78
+ tests: [],
79
+ status: "error",
80
+ errorMessage: message,
81
+ };
82
+ }
83
+ }
84
+ /**
85
+ * Builds a directive guidance block for the calling LLM when one or more
86
+ * sessions did not return usable test code. Each status block contains:
87
+ * - A diagnosis (what happened, per BrowserStack)
88
+ * - An explicit "do NOT paraphrase this as X" constraint
89
+ * - A ready-to-say phrasing the LLM can quote when replying to the user
90
+ *
91
+ * Returns empty string when every session returned `status: ok`.
92
+ */
93
+ export function describeTestCodeFetchIssues(sessionTestCodes) {
94
+ if (sessionTestCodes.length === 0)
95
+ return "";
96
+ const byStatus = new Map();
97
+ for (const s of sessionTestCodes) {
98
+ if (!byStatus.has(s.status))
99
+ byStatus.set(s.status, []);
100
+ byStatus.get(s.status).push(s.sessionId);
101
+ }
102
+ const notes = [];
103
+ const nonSdk = byStatus.get("non_sdk_build");
104
+ if (nonSdk && nonSdk.length > 0) {
105
+ const ids = nonSdk.join(", ");
106
+ notes.push([
107
+ `### Non-SDK build — session(s): ${ids}`,
108
+ "",
109
+ "Diagnosis: BrowserStack's test-code API returned HTTP 200 with rows " +
110
+ "where `code` and `filename` are null. This is the KNOWN signature " +
111
+ "of a BrowserStack run that was NOT instrumented with the " +
112
+ "BrowserStack SDK / Observability. The API literally has no source " +
113
+ "code to return for these sessions — this is by design.",
114
+ "",
115
+ "DO NOT tell the user any of the following (all are wrong for this " +
116
+ "status): 'credentials issue', 'credentials or session issue', " +
117
+ "'could not fetch from the API due to auth', '401', 'unauthorized'. " +
118
+ "The credentials are fine; the build just isn't SDK-enabled.",
119
+ "",
120
+ "Say this (or very close to it) to the user, then wait for their reply:",
121
+ ' "This is a non-SDK BrowserStack build, so the test-code API has ' +
122
+ "no source to return for it (this is expected, not a credentials " +
123
+ "problem). Can you tell me the local file path where these tests live? " +
124
+ "Once I have the file, I'll apply the healed locators listed in the plan.\"",
125
+ ].join("\n"));
126
+ }
127
+ const unauthorized = byStatus.get("unauthorized");
128
+ if (unauthorized && unauthorized.length > 0) {
129
+ const ids = unauthorized.join(", ");
130
+ notes.push([
131
+ `### Unauthorized (HTTP 401) — session(s): ${ids}`,
132
+ "",
133
+ "Diagnosis: BrowserStack's test-code API rejected the credentials " +
134
+ "(HTTP 401). This IS an authentication problem for this specific " +
135
+ "API — the healing report endpoint and test-code endpoint use the " +
136
+ "same BrowserStack auth, so if the report fetch above succeeded " +
137
+ "with the same creds, suspect that the access key configured on " +
138
+ "the MCP server was rotated.",
139
+ "",
140
+ "DO NOT say 'credentials or session issue' (that hides the real cause). " +
141
+ "Name the 401 explicitly and offer the user a choice. Do NOT ask " +
142
+ "the user to paste a BrowserStack username or access key in chat — " +
143
+ "the MCP server reads credentials from its own environment.",
144
+ "",
145
+ "Say this (or very close to it) to the user, then wait for their reply:",
146
+ ' "The BrowserStack test-code API returned 401 Unauthorized for ' +
147
+ `session(s) ${ids}. Would you like to: (a) update the BrowserStack ` +
148
+ "username and access key on the MCP server (BROWSERSTACK_USERNAME / " +
149
+ "BROWSERSTACK_ACCESS_KEY) and restart it, or (b) skip the API and " +
150
+ 'point me at the local test file so I can apply the healed locators there?"',
151
+ ].join("\n"));
152
+ }
153
+ const forbidden = byStatus.get("forbidden");
154
+ if (forbidden && forbidden.length > 0) {
155
+ const ids = forbidden.join(", ");
156
+ notes.push([
157
+ `### Forbidden (HTTP 403) — session(s): ${ids}`,
158
+ "",
159
+ "Diagnosis: BrowserStack's test-code API accepted the credentials " +
160
+ "but denied access to the session's source (HTTP 403). Typically " +
161
+ "the user does not own this session, or the account does not have " +
162
+ "Observability enabled.",
163
+ "",
164
+ "Do NOT blame the credentials broadly — say 'access denied for this " +
165
+ "session' and move on.",
166
+ "",
167
+ "Say this (or very close to it) to the user:",
168
+ ` "BrowserStack denied access (HTTP 403) to session(s) ${ids}. ` +
169
+ "This usually means the account does not own these sessions or " +
170
+ "does not have Observability enabled. Can you either share the " +
171
+ 'local test file path, or confirm which account these sessions belong to?"',
172
+ ].join("\n"));
173
+ }
174
+ const notFound = byStatus.get("not_found");
175
+ if (notFound && notFound.length > 0) {
176
+ const ids = notFound.join(", ");
177
+ notes.push([
178
+ `### Not found (HTTP 404) — session(s): ${ids}`,
179
+ "",
180
+ "Diagnosis: BrowserStack returned HTTP 404 — the session id is most " +
181
+ "likely wrong, or the session has been purged.",
182
+ "",
183
+ "Do NOT say 'credentials'. Ask the user to verify the id.",
184
+ "",
185
+ "Say this (or very close to it) to the user:",
186
+ ` "I couldn't find session(s) ${ids} on BrowserStack (HTTP 404). ` +
187
+ "Can you double-check the session id(s), or share the local test " +
188
+ 'file directly so I can apply the healed locators?"',
189
+ ].join("\n"));
190
+ }
191
+ const empty = byStatus.get("empty");
192
+ if (empty && empty.length > 0) {
193
+ const ids = empty.join(", ");
194
+ notes.push([
195
+ `### No test runs recorded — session(s): ${ids}`,
196
+ "",
197
+ "Diagnosis: HTTP 200 with an empty array — the session executed but " +
198
+ "no test code was captured (e.g. a raw Selenium session not linked " +
199
+ "to a test framework).",
200
+ "",
201
+ "Say this (or very close to it) to the user:",
202
+ ` "BrowserStack has no test code recorded for session(s) ${ids}. ` +
203
+ 'Can you point me at the local test file where these locators live?"',
204
+ ].join("\n"));
205
+ }
206
+ const errored = byStatus.get("error");
207
+ if (errored && errored.length > 0) {
208
+ const details = sessionTestCodes
209
+ .filter((s) => s.status === "error")
210
+ .map((s) => `${s.sessionId}: ${s.errorMessage ?? "unknown error"}`)
211
+ .join("; ");
212
+ notes.push([
213
+ `### Transport error — session(s): ${errored.join(", ")}`,
214
+ "",
215
+ `Diagnosis: network/transport failure — ${details}. This is not an ` +
216
+ "auth issue.",
217
+ "",
218
+ "Say this (or very close to it) to the user:",
219
+ ` "I hit a transport error fetching test code from BrowserStack ` +
220
+ `(${details}). Want me to retry, or would you prefer to share the ` +
221
+ 'local test file directly?"',
222
+ ].join("\n"));
223
+ }
224
+ return notes.join("\n\n");
225
+ }
226
+ /**
227
+ * Fetches test code for multiple sessions in parallel.
228
+ * Failures for individual sessions are logged and skipped (partial results).
229
+ */
230
+ export async function fetchTestCodeForSessions(sessionIds, config) {
231
+ const results = await Promise.allSettled(sessionIds.map((id) => fetchTestCodeBySession(id, config)));
232
+ return results
233
+ .filter((r) => r.status === "fulfilled")
234
+ .map((r) => r.value);
235
+ }
236
+ /**
237
+ * Formats test code entries into a concise context string suitable for
238
+ * inclusion in an LLM prompt. Groups by file, includes code snippets.
239
+ */
240
+ export function formatTestCodeAsContext(sessionTestCodes) {
241
+ const sections = [];
242
+ for (const session of sessionTestCodes) {
243
+ const testsWithCode = session.tests.filter((t) => t.code);
244
+ if (testsWithCode.length === 0)
245
+ continue;
246
+ const testLines = testsWithCode.map((t) => {
247
+ const fileInfo = t.filename ? `File: ${t.filename}` : "File: unknown";
248
+ const urlInfo = t.url ? `URL: ${t.url}` : "";
249
+ const header = [fileInfo, urlInfo].filter(Boolean).join("\n");
250
+ return `${header}\n\`\`\`\n${t.code}\n\`\`\``;
251
+ });
252
+ sections.push(`Session: ${session.sessionId}\n${testLines.join("\n\n")}`);
253
+ }
254
+ if (sections.length === 0) {
255
+ return "";
256
+ }
257
+ return `\n--- Test Code Context ---\n${sections.join("\n\n")}\n--- End Test Code Context ---\n`;
258
+ }
@@ -1,11 +1,55 @@
1
1
  interface SelectorMapping {
2
2
  originalSelector: string;
3
3
  healedSelector: string;
4
+ selectorType: string;
5
+ healedSelectorType: string;
4
6
  context: {
5
7
  before: string;
6
8
  after: string;
7
9
  };
8
10
  }
11
+ export interface LocatorRef {
12
+ type: string;
13
+ value: string;
14
+ }
15
+ export interface HealedSelectorEntry {
16
+ original_locator: LocatorRef;
17
+ healed_locator: LocatorRef;
18
+ timestamp?: string;
19
+ healing_thought?: string;
20
+ healed_element_screenshot_url?: string;
21
+ }
22
+ export interface HealingLog {
23
+ session_id: string;
24
+ session_name?: string;
25
+ product?: string;
26
+ os?: string;
27
+ browser?: string;
28
+ browser_version?: string;
29
+ healed_selectors: HealedSelectorEntry[];
30
+ healing_attempted_logs?: unknown[];
31
+ session_url?: string;
32
+ }
33
+ export interface SelfHealingReport {
34
+ report_meta: Record<string, unknown>;
35
+ healing_logs: HealingLog[];
36
+ }
9
37
  import { BrowserStackConfig } from "../../lib/types.js";
10
- export declare function getSelfHealSelectors(sessionId: string, config: BrowserStackConfig): Promise<SelectorMapping[]>;
38
+ type SessionType = "automate" | "app-automate";
39
+ /**
40
+ * Fetches the self-healing report for a build via the build-scoped API.
41
+ *
42
+ * Two-step flow:
43
+ * 1. GET /ext/v1/builds/{buildUuid}/selfHealingReport -> { url, expiry }
44
+ * 2. GET <presigned S3 url> -> full report JSON
45
+ *
46
+ * Returns the full report so the caller (LLM) has rich context (metrics,
47
+ * screenshots, healing thoughts) when deciding which selectors to apply.
48
+ */
49
+ export declare function fetchSelfHealingReportByBuild(buildUuid: string, config: BrowserStackConfig): Promise<SelfHealingReport>;
50
+ /**
51
+ * Convenience: flatten all healed_selectors across sessions in a report.
52
+ */
53
+ export declare function flattenHealedSelectors(report: SelfHealingReport): HealedSelectorEntry[];
54
+ export declare function getSelfHealSelectors(sessionId: string, config: BrowserStackConfig, sessionType?: SessionType): Promise<SelectorMapping[]>;
11
55
  export {};
@@ -1,10 +1,73 @@
1
1
  import { assertOkResponse } from "../../lib/utils.js";
2
2
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
3
3
  import { apiClient } from "../../lib/apiClient.js";
4
- export async function getSelfHealSelectors(sessionId, config) {
4
+ const SELF_HEAL_REPORT_BASE = "https://api-automation.browserstack.com/ext/v1/builds";
5
+ /**
6
+ * Fetches the self-healing report for a build via the build-scoped API.
7
+ *
8
+ * Two-step flow:
9
+ * 1. GET /ext/v1/builds/{buildUuid}/selfHealingReport -> { url, expiry }
10
+ * 2. GET <presigned S3 url> -> full report JSON
11
+ *
12
+ * Returns the full report so the caller (LLM) has rich context (metrics,
13
+ * screenshots, healing thoughts) when deciding which selectors to apply.
14
+ */
15
+ export async function fetchSelfHealingReportByBuild(buildUuid, config) {
16
+ if (!buildUuid) {
17
+ throw new Error("buildUuid is required");
18
+ }
5
19
  const authString = getBrowserStackAuth(config);
6
20
  const auth = Buffer.from(authString).toString("base64");
7
- const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
21
+ const presignedResp = await apiClient.get({
22
+ url: `${SELF_HEAL_REPORT_BASE}/${encodeURIComponent(buildUuid)}/selfHealingReport`,
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ Authorization: `Basic ${auth}`,
26
+ },
27
+ });
28
+ await assertOkResponse(presignedResp, "self-healing report");
29
+ const presignedUrl = extractPresignedUrl(presignedResp.data);
30
+ if (!presignedUrl) {
31
+ throw new Error("Self-healing report response did not contain a presigned URL");
32
+ }
33
+ const reportResp = await apiClient.get({
34
+ url: presignedUrl,
35
+ });
36
+ await assertOkResponse(reportResp, "self-healing report payload");
37
+ return reportResp.data;
38
+ }
39
+ function extractPresignedUrl(payload) {
40
+ if (!payload || typeof payload !== "object")
41
+ return undefined;
42
+ const candidates = ["url", "presigned_url", "signed_url", "s3_url"];
43
+ const obj = payload;
44
+ for (const key of candidates) {
45
+ const value = obj[key];
46
+ if (typeof value === "string" && /^https?:\/\//.test(value)) {
47
+ return value;
48
+ }
49
+ }
50
+ // Some APIs nest the URL one level deeper (e.g. { data: { url } }).
51
+ for (const value of Object.values(obj)) {
52
+ if (value && typeof value === "object") {
53
+ const nested = extractPresignedUrl(value);
54
+ if (nested)
55
+ return nested;
56
+ }
57
+ }
58
+ return undefined;
59
+ }
60
+ /**
61
+ * Convenience: flatten all healed_selectors across sessions in a report.
62
+ */
63
+ export function flattenHealedSelectors(report) {
64
+ return (report.healing_logs ?? []).flatMap((log) => log.healed_selectors ?? []);
65
+ }
66
+ export async function getSelfHealSelectors(sessionId, config, sessionType = "automate") {
67
+ const authString = getBrowserStackAuth(config);
68
+ const auth = Buffer.from(authString).toString("base64");
69
+ const productPath = sessionType === "app-automate" ? "app-automate" : "automate";
70
+ const url = `https://api.browserstack.com/${productPath}/sessions/${sessionId}/logs`;
8
71
  const response = await apiClient.get({
9
72
  url,
10
73
  headers: {
@@ -22,34 +85,67 @@ function extractHealedSelectors(logText) {
22
85
  // Split log text into lines for easier context handling
23
86
  const logLines = logText.split("\n");
24
87
  // Pattern to match successful SELFHEAL entries only
25
- const selfhealPattern = /SELFHEAL\s*{\s*"status":"true",\s*"data":\s*{\s*"using":"css selector",\s*"value":"(.*?)"}/;
26
- // Pattern to match preceding selector requests
27
- const requestPattern = /POST \/session\/[^/]+\/element.*?"using":"css selector","value":"(.*?)"/;
88
+ const selfhealPattern = /SELFHEAL\s*{\s*"status":"true",\s*"data":\s*{\s*"using":"([^"]+)",\s*"value":"(.*?)"}/;
28
89
  // Find all successful healed selectors with their line numbers and context
29
90
  const healedMappings = [];
30
91
  for (let i = 0; i < logLines.length; i++) {
31
92
  const match = logLines[i].match(selfhealPattern);
32
- if (match) {
33
- const beforeLine = i > 0 ? logLines[i - 1] : "";
34
- const afterLine = i < logLines.length - 1 ? logLines[i + 1] : "";
35
- // Look backwards to find the most recent original selector request
36
- let originalSelector = "UNKNOWN";
37
- for (let j = i - 1; j >= 0; j--) {
38
- const requestMatch = logLines[j].match(requestPattern);
39
- if (requestMatch) {
40
- originalSelector = requestMatch[1];
41
- break;
42
- }
43
- }
44
- healedMappings.push({
45
- originalSelector,
46
- healedSelector: match[1],
47
- context: {
48
- before: beforeLine,
49
- after: afterLine,
50
- },
51
- });
93
+ if (!match) {
94
+ continue;
52
95
  }
96
+ const beforeLine = i > 0 ? logLines[i - 1] : "";
97
+ const afterLine = i < logLines.length - 1 ? logLines[i + 1] : "";
98
+ const healedSelectorType = normalizeSelectorType(match[1]);
99
+ const healedSelector = cleanSelectorValue(match[2]);
100
+ const requestLine = findClosestRequestLine(logLines, i);
101
+ const requestSelector = requestLine
102
+ ? extractSelectorFromRequest(requestLine)
103
+ : {
104
+ selector: "UNKNOWN",
105
+ selectorType: "unknown",
106
+ };
107
+ healedMappings.push({
108
+ originalSelector: requestSelector.selector,
109
+ healedSelector,
110
+ selectorType: requestSelector.selectorType,
111
+ healedSelectorType,
112
+ context: {
113
+ before: beforeLine,
114
+ after: afterLine,
115
+ },
116
+ });
53
117
  }
54
118
  return healedMappings;
55
119
  }
120
+ function findClosestRequestLine(logLines, currentIndex) {
121
+ for (let i = currentIndex - 1; i >= 0; i--) {
122
+ const line = logLines[i];
123
+ if (line.includes("REQUEST") && line.includes('"using"')) {
124
+ return line;
125
+ }
126
+ if (line.includes("SELFHEAL")) {
127
+ break;
128
+ }
129
+ }
130
+ return undefined;
131
+ }
132
+ function extractSelectorFromRequest(line) {
133
+ const usingMatch = line.match(/"using":"([^"]+)"/);
134
+ const valueMatch = line.match(/"value":"(.*?)"/);
135
+ if (usingMatch && valueMatch) {
136
+ return {
137
+ selector: cleanSelectorValue(valueMatch[1]),
138
+ selectorType: normalizeSelectorType(usingMatch[1]),
139
+ };
140
+ }
141
+ return {
142
+ selector: "UNKNOWN",
143
+ selectorType: "unknown",
144
+ };
145
+ }
146
+ function cleanSelectorValue(value) {
147
+ return value.replace(/\\\\/g, "\\");
148
+ }
149
+ function normalizeSelectorType(value) {
150
+ return value ? value.toLowerCase() : "unknown";
151
+ }
@@ -1,7 +1,32 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { BrowserStackConfig } from "../lib/types.js";
4
- export declare function fetchSelfHealSelectorTool(args: {
4
+ type SessionType = "automate" | "app-automate";
5
+ interface FetchArgs {
6
+ sessionId?: string;
7
+ sessionType?: SessionType;
8
+ buildUuid?: string;
9
+ }
10
+ export declare function fetchSelfHealSelectorTool(args: FetchArgs, config: BrowserStackConfig): Promise<CallToolResult>;
11
+ interface SessionLocator {
12
+ original: {
13
+ strategy: string;
14
+ value: string;
15
+ };
16
+ healed: {
17
+ strategy: string;
18
+ value: string;
19
+ };
20
+ thought?: string;
21
+ }
22
+ interface SessionPayload {
5
23
  sessionId: string;
6
- }, config: BrowserStackConfig): Promise<CallToolResult>;
24
+ sessionName?: string;
25
+ locators: SessionLocator[];
26
+ }
27
+ interface PlanArgs {
28
+ sessions: SessionPayload[];
29
+ }
30
+ export declare function prepareSelfHealingPlanTool(args: PlanArgs, config: BrowserStackConfig): Promise<CallToolResult>;
7
31
  export default function addSelfHealTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
32
+ export {};