Skip to main content
Glama

BrowserStack MCP server

Official
rca-data.ts7.54 kB
import { RCAState, RCATestCase, RCAResponse } from "./types.js"; interface ScanProgressContext { sendNotification: (notification: any) => Promise<void>; _meta?: { progressToken?: string | number; }; } const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); function isInProgressState(state: RCAState): boolean { return [ RCAState.PENDING, RCAState.FETCHING_LOGS, RCAState.GENERATING_RCA, RCAState.GENERATED_RCA, ].includes(state); } function isFailedState(state: RCAState): boolean { return [ RCAState.FAILED, RCAState.LLM_SERVICE_ERROR, RCAState.LOG_FETCH_ERROR, RCAState.UNKNOWN_ERROR, RCAState.TIMEOUT, ].includes(state); } function calculateProgress( resolvedCount: number, totalCount: number, baseProgress: number = 10, ): number { if (totalCount === 0) return 100; // ✅ fix divide by zero const progressRange = 90 - baseProgress; const completionProgress = (resolvedCount / totalCount) * progressRange; return Math.min(100, baseProgress + completionProgress); } // ✅ centralized mapping function function mapApiState(apiState?: string): RCAState { const state = apiState?.toLowerCase(); switch (state) { case "completed": return RCAState.COMPLETED; case "pending": return RCAState.PENDING; case "fetching_logs": return RCAState.FETCHING_LOGS; case "generating_rca": return RCAState.GENERATING_RCA; case "generated_rca": return RCAState.GENERATED_RCA; case "error": return RCAState.UNKNOWN_ERROR; default: return RCAState.UNKNOWN_ERROR; } } async function notifyProgress( context: ScanProgressContext | undefined, message: string, progress: number, ) { if (!context?.sendNotification) return; await context.sendNotification({ method: "notifications/progress", params: { progressToken: context._meta?.progressToken?.toString(), message, progress, total: 100, }, }); } async function updateProgress( context: ScanProgressContext | undefined, testCases: RCATestCase[], message?: string, ) { const inProgressCases = testCases.filter((tc) => isInProgressState(tc.state)); const resolvedCount = testCases.length - inProgressCases.length; await notifyProgress( context, message ?? `RCA analysis in progress (${resolvedCount}/${testCases.length} resolved)`, inProgressCases.length === 0 ? 100 : calculateProgress(resolvedCount, testCases.length), ); } async function fetchInitialRCA( testId: number, headers: Record<string, string>, baseUrl: string, ): Promise<RCATestCase> { const url = baseUrl.replace("{testId}", testId.toString()); try { const response = await fetch(url, { headers }); if (!response.ok) { return { id: testId, testRunId: testId, state: RCAState.LOG_FETCH_ERROR, rcaData: { error: `HTTP ${response.status}: Failed to start RCA analysis`, }, }; } const data = await response.json(); const resultState = mapApiState(data.state); return { id: testId, testRunId: testId, state: resultState, ...(resultState === RCAState.COMPLETED && { rcaData: data }), ...(isFailedState(resultState) && data.state && { rcaData: { error: `API returned state: ${data.state}`, originalResponse: data, }, }), }; } catch (error) { return { id: testId, testRunId: testId, state: RCAState.LLM_SERVICE_ERROR, rcaData: { error: error instanceof Error ? error.message : "Network or parsing error", }, }; } } async function pollRCAResults( testCases: RCATestCase[], headers: Record<string, string>, baseUrl: string, context: ScanProgressContext | undefined, pollInterval: number, timeout: number, initialDelay: number, ): Promise<RCAResponse> { const startTime = Date.now(); await delay(initialDelay); try { while (true) { const inProgressCases = testCases.filter((tc) => isInProgressState(tc.state), ); await updateProgress(context, testCases); if (inProgressCases.length === 0) break; if (Date.now() - startTime >= timeout) { inProgressCases.forEach((tc) => { tc.state = RCAState.TIMEOUT; tc.rcaData = { error: `Timeout after ${timeout}ms` }; }); await updateProgress(context, testCases, "RCA analysis timed out"); break; } await Promise.allSettled( inProgressCases.map(async (tc) => { try { const pollUrl = baseUrl.replace("{testId}", tc.id.toString()); const response = await fetch(pollUrl, { headers }); if (!response.ok) { const errorText = await response.text(); tc.state = RCAState.LOG_FETCH_ERROR; tc.rcaData = { error: `HTTP ${response.status}: Polling failed - ${errorText}`, }; return; } const data = await response.json(); if (!isFailedState(tc.state)) { const mappedState = mapApiState(data.state); tc.state = mappedState; if (mappedState === RCAState.COMPLETED) { tc.rcaData = data; } else if (mappedState === RCAState.UNKNOWN_ERROR) { tc.rcaData = { error: `API returned state: ${data.state}`, originalResponse: data, }; } } } catch (err) { if (!isFailedState(tc.state)) { tc.state = RCAState.LLM_SERVICE_ERROR; tc.rcaData = { error: err instanceof Error ? err.message : "Network or parsing error", }; } } }), ); await delay(pollInterval); } } catch (err) { testCases .filter((tc) => isInProgressState(tc.state)) .forEach((tc) => { tc.state = RCAState.UNKNOWN_ERROR; tc.rcaData = { error: err instanceof Error ? err.message : "Unexpected error", }; }); await updateProgress( context, testCases, "RCA analysis failed due to unexpected error", ); } return { testCases }; } export async function getRCAData( testIds: number[], authString: string, context?: ScanProgressContext, ): Promise<RCAResponse> { const pollInterval = 5000; const timeout = 40000; const initialDelay = 20000; const baseUrl = "https://api-observability.browserstack.com/ext/v1/testRun/{testId}/testRca"; const headers = { Authorization: `Basic ${Buffer.from(authString).toString("base64")}`, "Content-Type": "application/json", }; await notifyProgress(context, "Starting RCA analysis for test cases...", 0); const testCases = await Promise.all( testIds.map((testId) => fetchInitialRCA(testId, headers, baseUrl)), ); const inProgressCount = testCases.filter((tc) => isInProgressState(tc.state), ).length; await notifyProgress( context, `Initial RCA requests completed. ${inProgressCount} cases pending analysis...`, 10, ); if (inProgressCount === 0) return { testCases }; return await pollRCAResults( testCases, headers, baseUrl, context, pollInterval, timeout, initialDelay, ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/browserstack/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server