From 1f4f3fb205aedb8c053607f309bb4e5f65652542 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Wed, 10 Sep 2025 22:17:01 +0530 Subject: [PATCH 1/7] fix : Instrumentation,refactoring and bump version --- src/lib/utils.ts | 27 ++++++ src/tools/build-insights.ts | 13 +-- src/tools/list-test-files.ts | 6 ++ src/tools/percy-sdk.ts | 89 ++++++++----------- .../percy-snapshot-utils/detect-test-files.ts | 32 +------ src/tools/rca-agent-utils/constants.ts | 52 +++++------ src/tools/rca-agent.ts | 41 ++------- src/tools/review-agent.ts | 4 - src/tools/run-percy-scan.ts | 10 +-- src/tools/sdk-utils/common/schema.ts | 6 +- src/tools/sdk-utils/handler.ts | 16 ++-- 11 files changed, 127 insertions(+), 169 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c12b70f..bed98a7 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,6 +2,9 @@ import sharp from "sharp"; import type { ApiResponse } from "./apiClient.js"; import { BrowserStackConfig } from "./types.js"; import { getBrowserStackAuth } from "./get-auth.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { trackMCP } from "../index.js"; export function sanitizeUrlParam(param: string): string { // Remove any characters that could be used for command injection @@ -62,3 +65,27 @@ export async function fetchFromBrowserStackAPI( return res.json(); } + +function errorContent(message: string): CallToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; +} + +export function handleMCPError( + toolName: string, + server: McpServer, + config: BrowserStackConfig, + error: unknown, +) { + trackMCP(toolName, server.server.getClientVersion()!, error, config); + + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + + const readableToolName = toolName.replace(/([A-Z])/g, " $1").toLowerCase(); + + return errorContent( + `Failed to ${readableToolName}: ${errorMessage}. Please open an issue on GitHub if the problem persists`, + ); +} diff --git a/src/tools/build-insights.ts b/src/tools/build-insights.ts index 2cf4db2..60f685f 100644 --- a/src/tools/build-insights.ts +++ b/src/tools/build-insights.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import logger from "../logger.js"; import { BrowserStackConfig } from "../lib/types.js"; -import { fetchFromBrowserStackAPI } from "../lib/utils.js"; +import { fetchFromBrowserStackAPI, handleMCPError } from "../lib/utils.js"; // Tool function that fetches build insights from two APIs export async function fetchBuildInsightsTool( @@ -80,16 +80,7 @@ export default function addBuildInsightsTools( try { return await fetchBuildInsightsTool(args, config); } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Unknown error"; - return { - content: [ - { - type: "text", - text: `Error during fetching build insights: ${errorMessage}`, - }, - ], - }; + return handleMCPError("fetchBuildInsights", server, config, error); } }, ); diff --git a/src/tools/list-test-files.ts b/src/tools/list-test-files.ts index 7fb1d89..d3fea93 100644 --- a/src/tools/list-test-files.ts +++ b/src/tools/list-test-files.ts @@ -7,6 +7,12 @@ export async function addListTestFiles(args: any): Promise { const { dirs, language, framework } = args; let testFiles: string[] = []; + if (!dirs || dirs.length === 0) { + throw new Error( + "No directories provided to add the test files. Please provide test directories to add percy snapshot commands.", + ); + } + for (const dir of dirs) { const files = await listTestFiles({ language, diff --git a/src/tools/percy-sdk.ts b/src/tools/percy-sdk.ts index 1af0a48..37d436f 100644 --- a/src/tools/percy-sdk.ts +++ b/src/tools/percy-sdk.ts @@ -1,6 +1,6 @@ import { trackMCP } from "../index.js"; import { BrowserStackConfig } from "../lib/types.js"; -import { fetchPercyChanges } from "./percy-change.js"; +import { fetchPercyChanges } from "./review-agent.js"; import { addListTestFiles } from "./list-test-files.js"; import { runPercyScan } from "./run-percy-scan.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -25,6 +25,7 @@ import { FetchPercyChangesParamsShape, ManagePercyBuildApprovalParamsShape, } from "./sdk-utils/common/schema.js"; +import { handleMCPError } from "../lib/utils.js"; export function registerPercyTools( server: McpServer, @@ -32,7 +33,6 @@ export function registerPercyTools( ) { const tools: Record = {}; - // Register setupPercyVisualTesting tools.setupPercyVisualTesting = server.tool( "setupPercyVisualTesting", SETUP_PERCY_DESCRIPTION, @@ -46,26 +46,11 @@ export function registerPercyTools( ); return setUpPercyHandler(args, config); } catch (error) { - trackMCP( - "setupPercyVisualTesting", - server.server.getClientVersion()!, - error, - config, - ); - return { - content: [ - { - type: "text", - text: error instanceof Error ? error.message : String(error), - }, - ], - isError: true, - }; + return handleMCPError("setupPercyVisualTesting", server, config, error); } }, ); - // Register addPercySnapshotCommands tools.addPercySnapshotCommands = server.tool( "addPercySnapshotCommands", PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, @@ -79,26 +64,16 @@ export function registerPercyTools( ); return await updateTestsWithPercyCommands(args); } catch (error) { - trackMCP( + return handleMCPError( "addPercySnapshotCommands", - server.server.getClientVersion()!, - error, + server, config, + error, ); - return { - content: [ - { - type: "text", - text: error instanceof Error ? error.message : String(error), - }, - ], - isError: true, - }; } }, ); - // Register listTestFiles tools.listTestFiles = server.tool( "listTestFiles", LIST_TEST_FILES_DESCRIPTION, @@ -108,21 +83,7 @@ export function registerPercyTools( trackMCP("listTestFiles", server.server.getClientVersion()!, config); return addListTestFiles(args); } catch (error) { - trackMCP( - "listTestFiles", - server.server.getClientVersion()!, - error, - config, - ); - return { - content: [ - { - type: "text", - text: error instanceof Error ? error.message : String(error), - }, - ], - isError: true, - }; + return handleMCPError("listTestFiles", server, config, error); } }, ); @@ -132,16 +93,30 @@ export function registerPercyTools( "Run a Percy visual test scan. Example prompts : Run this Percy build/scan. Never run percy scan/build without this tool", RunPercyScanParamsShape, async (args) => { - return runPercyScan(args, config); + try { + trackMCP("runPercyScan", server.server.getClientVersion()!, config); + return runPercyScan(args, config); + } catch (error) { + return handleMCPError("runPercyScan", server, config, error); + } }, ); tools.fetchPercyChanges = server.tool( "fetchPercyChanges", - "Retrieves and summarizes all visual changes detected by Percy between the latest and previous builds, helping quickly review what has changed in your project.", + "Retrieves and summarizes all visual changes detected by Percy AI between the latest and previous builds, helping quickly review what has changed in your project.", FetchPercyChangesParamsShape, async (args) => { - return await fetchPercyChanges(args, config); + try { + trackMCP( + "fetchPercyChanges", + server.server.getClientVersion()!, + config, + ); + return await fetchPercyChanges(args, config); + } catch (error) { + return handleMCPError("fetchPercyChanges", server, config, error); + } }, ); @@ -150,7 +125,21 @@ export function registerPercyTools( "Approve or reject a Percy build", ManagePercyBuildApprovalParamsShape, async (args) => { - return await approveOrDeclinePercyBuild(args, config); + try { + trackMCP( + "managePercyBuildApproval", + server.server.getClientVersion()!, + config, + ); + return await approveOrDeclinePercyBuild(args, config); + } catch (error) { + return handleMCPError( + "managePercyBuildApproval", + server, + config, + error, + ); + } }, ); diff --git a/src/tools/percy-snapshot-utils/detect-test-files.ts b/src/tools/percy-snapshot-utils/detect-test-files.ts index 876b738..40886d1 100644 --- a/src/tools/percy-snapshot-utils/detect-test-files.ts +++ b/src/tools/percy-snapshot-utils/detect-test-files.ts @@ -1,6 +1,5 @@ import fs from "fs"; import path from "path"; -import logger from "../../logger.js"; import { SDKSupportedLanguage, @@ -38,7 +37,7 @@ async function walkDir( } } } catch { - logger.error(`Failed to read directory: ${dir}`); + // ignore } return result; @@ -54,7 +53,6 @@ async function fileContainsRegex( const content = await fs.promises.readFile(filePath, "utf8"); return regexes.some((re) => re.test(content)); } catch { - logger.warn(`Failed to read file: ${filePath}`); return false; } } @@ -69,7 +67,6 @@ async function batchRegexCheck( regexes.length > 0 ? regexes.some((re) => re.test(content)) : false, ); } catch { - logger.warn(`Failed to read file: ${filePath}`); return regexGroups.map(() => false); } } @@ -110,7 +107,6 @@ export async function listTestFiles( const config = TEST_FILE_DETECTION[language]; if (!config) { - logger.error(`Unsupported language: ${language}`); return []; } @@ -135,7 +131,6 @@ export async function listTestFiles( if (config.namePatterns.some((pattern) => pattern.test(fileName))) { candidateFiles.set(file, score); - logger.debug(`File matched by name pattern: ${file} (score: ${score})`); } } @@ -147,7 +142,6 @@ export async function listTestFiles( const fileName = path.basename(file); const score = getFileScore(fileName, config); candidateFiles.set(file, score); - logger.debug(`File matched by content regex: ${file} (score: ${score})`); } }); @@ -158,16 +152,12 @@ export async function listTestFiles( try { const featureFiles = await walkDir(baseDir, [".feature"], 6); featureFiles.forEach((file) => candidateFiles.set(file, 2)); - logger.info(`Added ${featureFiles.length} SpecFlow .feature files`); } catch { - logger.warn( - `Failed to collect SpecFlow .feature files from baseDir: ${baseDir}`, - ); + // ignore } } if (candidateFiles.size === 0) { - logger.info("No test files found matching patterns"); return []; } @@ -185,7 +175,6 @@ export async function listTestFiles( const isUITest = await isLikelyUITest(file); if (isUITest) { - logger.debug(`File included - strong UI indicators: ${file}`); return file; } @@ -198,43 +187,29 @@ export async function listTestFiles( config.excludeRegex || [], ]); - // Skip if explicitly excluded (mocks, unit tests, etc.) if (shouldExclude) { - logger.debug(`File excluded by exclude regex: ${file}`); return null; } - // Skip backend tests in any mode if (hasBackend) { - logger.debug(`File excluded as backend test: ${file}`); return null; } - // Include if has explicit UI drivers if (hasExplicitUI) { - logger.debug(`File included - explicit UI drivers: ${file}`); return file; } - // Include if has UI indicators (for cases where drivers aren't explicitly imported) if (hasUIIndicators) { - logger.debug(`File included - UI indicators: ${file}`); return file; } - // In non-strict mode, include high-scoring test files even without explicit UI patterns if (!strictMode) { const score = candidateFiles.get(file) || 0; if (score >= 3) { - // High confidence UI test based on naming - logger.debug( - `File included - high confidence score: ${file} (score: ${score})`, - ); return file; } } - logger.debug(`File excluded - no UI patterns detected: ${file}`); return null; }); @@ -251,9 +226,6 @@ export async function listTestFiles( return scoreB - scoreA; }); - logger.info( - `Returning ${uiFiles.length} UI test files from ${candidateFiles.size} total test files`, - ); return uiFiles; } diff --git a/src/tools/rca-agent-utils/constants.ts b/src/tools/rca-agent-utils/constants.ts index 0ff64d9..f1ca985 100644 --- a/src/tools/rca-agent-utils/constants.ts +++ b/src/tools/rca-agent-utils/constants.ts @@ -2,36 +2,36 @@ import { z } from "zod"; import { TestStatus } from "./types.js"; export const FETCH_RCA_PARAMS = { - testId: z - .array(z.string()) - .max(3) - .describe( - "Array of test IDs to fetch RCA data for (maximum 3 IDs). If not provided, use the listTestIds tool get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed." - ), + testId: z + .array(z.string()) + .max(3) + .describe( + "Array of test IDs to fetch RCA data for (maximum 3 IDs). If not provided, use the listTestIds tool get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed.", + ), }; export const GET_BUILD_ID_PARAMS = { - projectName: z - .string() - .describe( - "The Browserstack project name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions" - ), - buildName: z - .string() - .describe( - "The Browserstack build name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions" - ), + projectName: z + .string() + .describe( + "The Browserstack project name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions", + ), + buildName: z + .string() + .describe( + "The Browserstack build name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions", + ), }; export const LIST_TEST_IDS_PARAMS = { - buildId: z - .string() - .describe( - "The Browserstack Build ID of the test run. If not known, use the getBuildId tool to fetch it using project and build name" - ), - status: z - .nativeEnum(TestStatus) - .describe( - "Filter tests by status. If not provided, all tests are returned. Example for RCA usecase always use failed status" - ), + buildId: z + .string() + .describe( + "The Browserstack Build ID of the test run. If not known, use the getBuildId tool to fetch it using project and build name", + ), + status: z + .nativeEnum(TestStatus) + .describe( + "Filter tests by status. If not provided, all tests are returned. Example for RCA usecase always use failed status", + ), }; diff --git a/src/tools/rca-agent.ts b/src/tools/rca-agent.ts index 3b723b8..f0cc93d 100644 --- a/src/tools/rca-agent.ts +++ b/src/tools/rca-agent.ts @@ -1,5 +1,4 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, LIST_TEST_IDS_PARAMS } from "./rca-agent-utils/constants.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import logger from "../logger.js"; import { BrowserStackConfig } from "../lib/types.js"; @@ -9,6 +8,12 @@ import { getTestIds } from "./rca-agent-utils/get-failed-test-id.js"; import { getRCAData } from "./rca-agent-utils/rca-data.js"; import { formatRCAData } from "./rca-agent-utils/format-rca.js"; import { TestStatus } from "./rca-agent-utils/types.js"; +import { handleMCPError } from "../lib/utils.js"; +import { + FETCH_RCA_PARAMS, + GET_BUILD_ID_PARAMS, + LIST_TEST_IDS_PARAMS, +} from "./rca-agent-utils/constants.js"; // Tool function to fetch build ID export async function getBuildIdTool( @@ -117,7 +122,6 @@ export async function listTestIdsTool( } } -// Registers the fetchRCA tool with the MCP server export default function addRCATools( server: McpServer, config: BrowserStackConfig, @@ -132,16 +136,7 @@ export default function addRCATools( try { return await fetchRCADataTool(args, config); } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Unknown error"; - return { - content: [ - { - type: "text", - text: `Error during fetching RCA data: ${errorMessage}`, - }, - ], - }; + return handleMCPError("fetchRCA", server, config, error); } }, ); @@ -154,16 +149,7 @@ export default function addRCATools( try { return await getBuildIdTool(args, config); } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Unknown error"; - return { - content: [ - { - type: "text", - text: `Error during fetching build ID: ${errorMessage}`, - }, - ], - }; + return handleMCPError("getBuildId", server, config, error); } }, ); @@ -176,16 +162,7 @@ export default function addRCATools( try { return await listTestIdsTool(args, config); } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Unknown error"; - return { - content: [ - { - type: "text", - text: `Error during listing test IDs: ${errorMessage}`, - }, - ], - }; + return handleMCPError("listTestIds", server, config, error); } }, ); diff --git a/src/tools/review-agent.ts b/src/tools/review-agent.ts index 4b8e4d2..cf87fef 100644 --- a/src/tools/review-agent.ts +++ b/src/tools/review-agent.ts @@ -1,4 +1,3 @@ -import logger from "../logger.js"; import { BrowserStackConfig } from "../lib/types.js"; import { getBrowserStackAuth } from "../lib/get-auth.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; @@ -57,9 +56,6 @@ export async function fetchPercyChanges( orgId, browserIds, ); - logger.info( - `Fetched ${snapshotIds.length} snapshot IDs for build: ${lastBuildId} as ${snapshotIds.join(", ")}`, - ); // Fetch all diffs concurrently and flatten results const allDiffs = await getPercySnapshotDiffs(snapshotIds, percyToken); diff --git a/src/tools/run-percy-scan.ts b/src/tools/run-percy-scan.ts index fac071a..32f10c1 100644 --- a/src/tools/run-percy-scan.ts +++ b/src/tools/run-percy-scan.ts @@ -29,11 +29,11 @@ export async function runPercyScan( steps.push( `Attempt to infer the project's test command from context (high confidence commands first): -- Java → mvn test -- Python → pytest -- Node.js → npm test or yarn test -- Cypress → cypress run -or from package.json scripts`, + - Java → mvn test + - Python → pytest + - Node.js → npm test or yarn test + - Cypress → cypress run + or from package.json scripts`, `Wrap the inferred command with Percy:\nnpx percy exec -- `, `If the test command cannot be inferred confidently, ask the user directly for the correct test command.`, ); diff --git a/src/tools/sdk-utils/common/schema.ts b/src/tools/sdk-utils/common/schema.ts index 754d30d..4f2bd8d 100644 --- a/src/tools/sdk-utils/common/schema.ts +++ b/src/tools/sdk-utils/common/schema.ts @@ -1,10 +1,10 @@ import { z } from "zod"; +import { PercyIntegrationTypeEnum } from "./types.js"; import { SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, SDKSupportedLanguageEnum, } from "./types.js"; -import { PercyIntegrationTypeEnum } from "./types.js"; export const SetUpPercyParamsShape = { projectName: z.string().describe("A unique name for your Percy project."), @@ -16,12 +16,12 @@ export const SetUpPercyParamsShape = { integrationType: z .nativeEnum(PercyIntegrationTypeEnum) .describe( - "Specifies whether to integrate with Percy Web or Percy Automate. If not explicitly provided, prompt the user to select the desired integration type.", + "Specify the Percy integration type: web (Percy Web) or automate (Percy Automate). If not provided, always prompt the user with: 'Please specify the Percy integration type.' Do not proceed without an explicit selection. Never use a default.", ), folderPaths: z .array(z.string()) .describe( - "An array of folder paths to include in which Percy will be integrated. If not provided, strictly inspect the code and return the folders which contain UI test cases.", + "An array of absolute folder paths containing UI test files. If not provided, analyze codebase for UI test folders by scanning for test patterns which contain UI test cases as per framework. Return empty array if none found.", ), }; diff --git a/src/tools/sdk-utils/handler.ts b/src/tools/sdk-utils/handler.ts index e744580..01a119f 100644 --- a/src/tools/sdk-utils/handler.ts +++ b/src/tools/sdk-utils/handler.ts @@ -1,11 +1,3 @@ -import { - SetUpPercySchema, - RunTestsOnBrowserStackSchema, -} from "./common/schema.js"; -import { - getBootstrapFailedMessage, - percyUnsupportedResult, -} from "./common/utils.js"; import { formatToolResult } from "./common/utils.js"; import { BrowserStackConfig } from "../../lib/types.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; @@ -17,6 +9,14 @@ import { runPercyAutomateOnly } from "./percy-automate/handler.js"; import { runBstackSDKOnly } from "./bstack/sdkHandler.js"; import { runPercyWithBrowserstackSDK } from "./percy-bstack/handler.js"; import { checkPercyIntegrationSupport } from "./common/utils.js"; +import { + SetUpPercySchema, + RunTestsOnBrowserStackSchema, +} from "./common/schema.js"; +import { + getBootstrapFailedMessage, + percyUnsupportedResult, +} from "./common/utils.js"; export async function runTestsOnBrowserStackHandler( rawInput: unknown, From 58c50acadaa40fe7b25929b0e156f23318131dd3 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Wed, 10 Sep 2025 22:20:16 +0530 Subject: [PATCH 2/7] chore: update version to 1.2.4 and add mcpName field --- package-lock.json | 4 ++-- package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4120c7e..3411e58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@browserstack/mcp-server", - "version": "1.2.3", + "version": "1.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@browserstack/mcp-server", - "version": "1.2.3", + "version": "1.2.4", "license": "ISC", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.4", diff --git a/package.json b/package.json index 34c9a47..339558c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "@browserstack/mcp-server", - "version": "1.2.3", + "version": "1.2.4", "description": "BrowserStack's Official MCP Server", + "mcpName": "io.github.browserstack/mcp-server", "main": "dist/index.js", "repository": { "type": "git", From a23d6c3bfc95d001af17224a394a081b1d818d79 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Wed, 10 Sep 2025 22:26:40 +0530 Subject: [PATCH 3/7] instrumentation ++ --- src/tools/bstack-sdk.ts | 9 ++++++++- src/tools/build-insights.ts | 2 ++ src/tools/rca-agent.ts | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/tools/bstack-sdk.ts b/src/tools/bstack-sdk.ts index 84a8432..cff0c6a 100644 --- a/src/tools/bstack-sdk.ts +++ b/src/tools/bstack-sdk.ts @@ -3,6 +3,8 @@ import { BrowserStackConfig } from "../lib/types.js"; import { RunTestsOnBrowserStackParamsShape } from "./sdk-utils/common/schema.js"; import { runTestsOnBrowserStackHandler } from "./sdk-utils/handler.js"; import { RUN_ON_BROWSERSTACK_DESCRIPTION } from "./sdk-utils/common/constants.js"; +import { handleMCPError } from "../lib/utils.js"; +import { trackMCP } from "../lib/instrumentation.js"; export function registerRunBrowserStackTestsTool( server: McpServer, @@ -15,7 +17,12 @@ export function registerRunBrowserStackTestsTool( RUN_ON_BROWSERSTACK_DESCRIPTION, RunTestsOnBrowserStackParamsShape, async (args) => { - return runTestsOnBrowserStackHandler(args, config); + try { + trackMCP("runTestsOnBrowserStack", server.server.getClientVersion()!, config); + return await runTestsOnBrowserStackHandler(args, config); + } catch (error) { + return handleMCPError("runTestsOnBrowserStack", server, config, error); + } }, ); diff --git a/src/tools/build-insights.ts b/src/tools/build-insights.ts index 60f685f..cbad320 100644 --- a/src/tools/build-insights.ts +++ b/src/tools/build-insights.ts @@ -4,6 +4,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import logger from "../logger.js"; import { BrowserStackConfig } from "../lib/types.js"; import { fetchFromBrowserStackAPI, handleMCPError } from "../lib/utils.js"; +import { trackMCP } from "../lib/instrumentation.js"; // Tool function that fetches build insights from two APIs export async function fetchBuildInsightsTool( @@ -78,6 +79,7 @@ export default function addBuildInsightsTools( }, async (args) => { try { + trackMCP("fetchBuildInsights", server.server.getClientVersion()!, config); return await fetchBuildInsightsTool(args, config); } catch (error) { return handleMCPError("fetchBuildInsights", server, config, error); diff --git a/src/tools/rca-agent.ts b/src/tools/rca-agent.ts index f0cc93d..888b414 100644 --- a/src/tools/rca-agent.ts +++ b/src/tools/rca-agent.ts @@ -9,6 +9,7 @@ import { getRCAData } from "./rca-agent-utils/rca-data.js"; import { formatRCAData } from "./rca-agent-utils/format-rca.js"; import { TestStatus } from "./rca-agent-utils/types.js"; import { handleMCPError } from "../lib/utils.js"; +import { trackMCP } from "../index.js"; import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, @@ -134,6 +135,7 @@ export default function addRCATools( FETCH_RCA_PARAMS, async (args) => { try { + trackMCP("fetchRCA", server.server.getClientVersion()!, config); return await fetchRCADataTool(args, config); } catch (error) { return handleMCPError("fetchRCA", server, config, error); @@ -147,6 +149,7 @@ export default function addRCATools( GET_BUILD_ID_PARAMS, async (args) => { try { + trackMCP("getBuildId", server.server.getClientVersion()!, config); return await getBuildIdTool(args, config); } catch (error) { return handleMCPError("getBuildId", server, config, error); @@ -160,6 +163,7 @@ export default function addRCATools( LIST_TEST_IDS_PARAMS, async (args) => { try { + trackMCP("listTestIds", server.server.getClientVersion()!, config); return await listTestIdsTool(args, config); } catch (error) { return handleMCPError("listTestIds", server, config, error); From a1c2f2681c054324bff9cbc85fa91f45e7b967a5 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Wed, 10 Sep 2025 23:58:39 +0530 Subject: [PATCH 4/7] linting ++ --- src/tools/bstack-sdk.ts | 6 +++++- src/tools/build-insights.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/tools/bstack-sdk.ts b/src/tools/bstack-sdk.ts index cff0c6a..a7de781 100644 --- a/src/tools/bstack-sdk.ts +++ b/src/tools/bstack-sdk.ts @@ -18,7 +18,11 @@ export function registerRunBrowserStackTestsTool( RunTestsOnBrowserStackParamsShape, async (args) => { try { - trackMCP("runTestsOnBrowserStack", server.server.getClientVersion()!, config); + trackMCP( + "runTestsOnBrowserStack", + server.server.getClientVersion()!, + config, + ); return await runTestsOnBrowserStackHandler(args, config); } catch (error) { return handleMCPError("runTestsOnBrowserStack", server, config, error); diff --git a/src/tools/build-insights.ts b/src/tools/build-insights.ts index cbad320..ed6e3d7 100644 --- a/src/tools/build-insights.ts +++ b/src/tools/build-insights.ts @@ -79,7 +79,11 @@ export default function addBuildInsightsTools( }, async (args) => { try { - trackMCP("fetchBuildInsights", server.server.getClientVersion()!, config); + trackMCP( + "fetchBuildInsights", + server.server.getClientVersion()!, + config, + ); return await fetchBuildInsightsTool(args, config); } catch (error) { return handleMCPError("fetchBuildInsights", server, config, error); From b67bea943e3353ed6a7a2d76f8f1bf929cef9a5d Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Thu, 11 Sep 2025 02:27:39 +0530 Subject: [PATCH 5/7] refactor: update params and improve descriptions --- src/tools/rca-agent-utils/constants.ts | 8 ++--- .../rca-agent-utils/get-failed-test-id.ts | 30 ++++++++++++------- src/tools/rca-agent-utils/types.ts | 5 ++++ src/tools/rca-agent.ts | 15 +++++----- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/tools/rca-agent-utils/constants.ts b/src/tools/rca-agent-utils/constants.ts index f1ca985..c7a69c0 100644 --- a/src/tools/rca-agent-utils/constants.ts +++ b/src/tools/rca-agent-utils/constants.ts @@ -11,15 +11,15 @@ export const FETCH_RCA_PARAMS = { }; export const GET_BUILD_ID_PARAMS = { - projectName: z + browserStackProjectName: z .string() .describe( - "The Browserstack project name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions", + "The BrowserStack project name used during test run creation. Action: First, check browserstack.yml or any equivalent project configuration files. If the project name is found, extract and return it. If it is not found or if there is any uncertainty, immediately prompt the user to provide the value. Do not infer, guess, or assume a default.", ), - buildName: z + browserStackBuildName: z .string() .describe( - "The Browserstack build name used while creation of test run. Check browserstack.yml or similar project configuration files. If found extract it and provide to user, IF not found or unsure, prompt the user for this value. Do not make assumptions", + "The BrowserStack build name used during test run creation. Action: First, check browserstack.yml or any equivalent project configuration files. If the build name is found, extract and return it. If it is not found or if there is any uncertainty, immediately prompt the user to provide the value. Do not infer, guess, or assume a default.", ), }; diff --git a/src/tools/rca-agent-utils/get-failed-test-id.ts b/src/tools/rca-agent-utils/get-failed-test-id.ts index 7e8e6fb..a474b2b 100644 --- a/src/tools/rca-agent-utils/get-failed-test-id.ts +++ b/src/tools/rca-agent-utils/get-failed-test-id.ts @@ -36,22 +36,25 @@ export async function getTestIds( // Extract failed IDs from current page if (data.hierarchy && data.hierarchy.length > 0) { - const currentFailedTests = extractFailedTestIds(data.hierarchy); + const currentFailedTests = extractFailedTestIds(data.hierarchy, status); allFailedTests = allFailedTests.concat(currentFailedTests); } // Check for pagination termination conditions - if (!data.pagination?.has_next || !data.pagination.next_page) { + if ( + !data.pagination?.has_next || + !data.pagination.next_page || + requestNumber >= 5 + ) { break; } - // Safety limit to prevent runaway requests - if (requestNumber >= 5) { - break; - } + const params: Record = { + next_page: data.pagination.next_page, + }; + if (status) params.test_statuses = status; - // Prepare next request - url = `${baseUrl}?next_page=${encodeURIComponent(data.pagination.next_page)}`; + url = `${baseUrl}?${new URLSearchParams(params).toString()}`; } // Return unique failed test IDs @@ -63,11 +66,14 @@ export async function getTestIds( } // Recursive function to extract failed test IDs from hierarchy -function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] { +function extractFailedTestIds( + hierarchy: TestDetails[], + status?: TestStatus, +): FailedTestInfo[] { let failedTests: FailedTestInfo[] = []; for (const node of hierarchy) { - if (node.details?.status === "failed" && node.details?.run_count) { + if (node.details?.status === status && node.details?.run_count) { if (node.details?.observability_url) { const idMatch = node.details.observability_url.match(/details=(\d+)/); if (idMatch) { @@ -80,7 +86,9 @@ function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] { } if (node.children && node.children.length > 0) { - failedTests = failedTests.concat(extractFailedTestIds(node.children)); + failedTests = failedTests.concat( + extractFailedTestIds(node.children, status), + ); } } diff --git a/src/tools/rca-agent-utils/types.ts b/src/tools/rca-agent-utils/types.ts index c03fbdf..b2795dd 100644 --- a/src/tools/rca-agent-utils/types.ts +++ b/src/tools/rca-agent-utils/types.ts @@ -48,3 +48,8 @@ export interface RCATestCase { export interface RCAResponse { testCases: RCATestCase[]; } + +export interface BuildIdArgs { + browserStackProjectName: string; + browserStackBuildName: string; +} diff --git a/src/tools/rca-agent.ts b/src/tools/rca-agent.ts index 888b414..4b341e4 100644 --- a/src/tools/rca-agent.ts +++ b/src/tools/rca-agent.ts @@ -10,6 +10,7 @@ import { formatRCAData } from "./rca-agent-utils/format-rca.js"; import { TestStatus } from "./rca-agent-utils/types.js"; import { handleMCPError } from "../lib/utils.js"; import { trackMCP } from "../index.js"; +import { BuildIdArgs } from "./rca-agent-utils/types.js"; import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, @@ -18,22 +19,22 @@ import { // Tool function to fetch build ID export async function getBuildIdTool( - args: { - projectName: string; - buildName: string; - }, + args: BuildIdArgs, config: BrowserStackConfig, ): Promise { try { - const { projectName, buildName } = args; + const { browserStackProjectName, browserStackBuildName } = args; + const authString = getBrowserStackAuth(config); const [username, accessKey] = authString.split(":"); + const buildId = await getBuildId( - projectName, - buildName, + browserStackProjectName, + browserStackBuildName, username, accessKey, ); + return { content: [ { From e8a653f49e797964fdbdabc5450878c856502c42 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Thu, 11 Sep 2025 21:28:58 +0530 Subject: [PATCH 6/7] refactor: enhance error handling in validateSupportforAppAutomate --- src/tools/appautomate-utils/appium-sdk/types.ts | 11 ++--------- src/tools/appautomate-utils/appium-sdk/utils.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/tools/appautomate-utils/appium-sdk/types.ts b/src/tools/appautomate-utils/appium-sdk/types.ts index e17f0da..d231a2a 100644 --- a/src/tools/appautomate-utils/appium-sdk/types.ts +++ b/src/tools/appautomate-utils/appium-sdk/types.ts @@ -60,15 +60,8 @@ export interface AppSDKInstruction { export const SUPPORTED_CONFIGURATIONS = { appium: { ruby: ["cucumberRuby"], - java: [ - "junit5", - "junit4", - "testng", - "cucumberTestng", - "selenide", - "jbehave", - ], - csharp: ["nunit", "xunit", "mstest", "specflow", "reqnroll"], + java: [], + csharp: [], python: ["pytest", "robot", "behave", "lettuce"], nodejs: ["jest", "mocha", "cucumberJs", "webdriverio", "nightwatch"], }, diff --git a/src/tools/appautomate-utils/appium-sdk/utils.ts b/src/tools/appautomate-utils/appium-sdk/utils.ts index 9c042ba..3c532da 100644 --- a/src/tools/appautomate-utils/appium-sdk/utils.ts +++ b/src/tools/appautomate-utils/appium-sdk/utils.ts @@ -96,7 +96,15 @@ export function validateSupportforAppAutomate( ); } - const testingFrameworks = SUPPORTED_CONFIGURATIONS[framework][language]; + const testingFrameworks = SUPPORTED_CONFIGURATIONS[framework][ + language + ] as string[]; + + if (testingFrameworks.length === 0) { + throw new Error( + `No testing frameworks are supported for language '${language}' and framework '${framework}'.`, + ); + } if (!testingFrameworks.includes(testingFramework)) { throw new Error( `Unsupported testing framework '${testingFramework}' for language '${language}' and framework '${framework}'. Supported testing frameworks: ${testingFrameworks.join(", ")}`, From 6532db2f62423eb11bca4a5d42cef5d92734a2d3 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Fri, 12 Sep 2025 13:02:49 +0530 Subject: [PATCH 7/7] refactor: add logging for directory read failures in walkDir function --- src/tools/percy-snapshot-utils/detect-test-files.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/percy-snapshot-utils/detect-test-files.ts b/src/tools/percy-snapshot-utils/detect-test-files.ts index 40886d1..999790f 100644 --- a/src/tools/percy-snapshot-utils/detect-test-files.ts +++ b/src/tools/percy-snapshot-utils/detect-test-files.ts @@ -14,6 +14,7 @@ import { } from "../percy-snapshot-utils/constants.js"; import { DetectionConfig } from "../percy-snapshot-utils/types.js"; +import logger from "../../logger.js"; async function walkDir( dir: string, @@ -37,7 +38,7 @@ async function walkDir( } } } catch { - // ignore + logger.info("Failed to read user directory"); } return result;