From 174c7d40a8be5807430222bb588d47f0f50b750d Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 2 Jun 2025 15:42:12 +0530 Subject: [PATCH 1/5] feat: refactor API endpoints to use centralized domain constants --- src/index.ts | 3 ++ src/lib/api.ts | 3 +- src/lib/device-cache.ts | 10 ++-- src/lib/domains.ts | 17 +++++++ src/lib/instrumentation.ts | 3 +- src/logger.ts | 50 +++++++------------ src/tools/accessibility.ts | 5 +- .../accessiblity-utils/report-fetcher.ts | 5 +- src/tools/accessiblity-utils/scanner.ts | 7 +-- src/tools/appautomate-utils/appautomate.ts | 3 +- src/tools/applive-utils/start-session.ts | 3 +- src/tools/applive-utils/upload-app.ts | 4 +- src/tools/automate-utils/fetch-screenshots.ts | 3 +- src/tools/failurelogs-utils/app-automate.ts | 7 +-- src/tools/failurelogs-utils/automate.ts | 7 +-- src/tools/live-utils/start-session.ts | 5 +- src/tools/selfheal-utils/selfheal.ts | 4 +- .../testmanagement-utils/TCG-utils/api.ts | 5 +- .../testmanagement-utils/TCG-utils/config.ts | 15 +++--- .../testmanagement-utils/add-test-result.ts | 4 +- .../create-project-folder.ts | 9 ++-- .../testmanagement-utils/create-testcase.ts | 5 +- .../testmanagement-utils/create-testrun.ts | 3 +- .../testmanagement-utils/list-testcases.ts | 3 +- .../testmanagement-utils/list-testruns.ts | 3 +- .../testmanagement-utils/update-testrun.ts | 3 +- src/tools/testmanagement-utils/upload-file.ts | 3 +- 27 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 src/lib/domains.ts diff --git a/src/index.ts b/src/index.ts index bfad2f9..f255070 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import addFailureLogsTools from "./tools/getFailureLogs.js"; import addAutomateTools from "./tools/automate.js"; import addSelfHealTools from "./tools/selfheal.js"; import { setupOnInitialized } from "./oninitialized.js"; +import { DOMAINS } from "./lib/domains.js"; function registerTools(server: McpServer) { addSDKTools(server); @@ -30,6 +31,8 @@ function registerTools(server: McpServer) { addSelfHealTools(server); } +console.log(DOMAINS.APP_LIVE); + // Create an MCP server const server: McpServer = new McpServer({ name: "BrowserStack MCP Server", diff --git a/src/lib/api.ts b/src/lib/api.ts index af453f1..a29e642 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,10 +1,11 @@ import config from "../config.js"; +import { DOMAINS } from "./domains.js"; export async function getLatestO11YBuildInfo( buildName: string, projectName: string, ) { - const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent( + const buildsUrl = `${DOMAINS.API_OBSERVABILITY}/ext/v1/builds/latest?build_name=${encodeURIComponent( buildName, )}&project_name=${encodeURIComponent(projectName)}`; diff --git a/src/lib/device-cache.ts b/src/lib/device-cache.ts index 5131bb5..599518b 100644 --- a/src/lib/device-cache.ts +++ b/src/lib/device-cache.ts @@ -1,6 +1,7 @@ import fs from "fs"; import os from "os"; import path from "path"; +import { DOMAINS } from "./domains.js"; const CACHE_DIR = path.join(os.homedir(), ".browserstack", "combined_cache"); const CACHE_FILE = path.join(CACHE_DIR, "data.json"); @@ -13,12 +14,9 @@ export enum BrowserStackProducts { } const URLS: Record = { - [BrowserStackProducts.LIVE]: - "https://www.browserstack.com/list-of-browsers-and-platforms/live.json", - [BrowserStackProducts.APP_LIVE]: - "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json", - [BrowserStackProducts.APP_AUTOMATE]: - "https://www.browserstack.com/list-of-browsers-and-platforms/app_automate.json", + [BrowserStackProducts.LIVE]: `${DOMAINS.WWW}/list-of-browsers-and-platforms/live.json`, + [BrowserStackProducts.APP_LIVE]: `${DOMAINS.WWW}/list-of-browsers-and-platforms/app_live.json`, + [BrowserStackProducts.APP_AUTOMATE]: `${DOMAINS.WWW}/list-of-browsers-and-platforms/app_automate.json`, }; /** diff --git a/src/lib/domains.ts b/src/lib/domains.ts new file mode 100644 index 0000000..fa946c3 --- /dev/null +++ b/src/lib/domains.ts @@ -0,0 +1,17 @@ +export const DOMAINS = { + API: process.env.API_DOMAIN || "https://api.browserstack.com", + API_CLOUD: + process.env.API_CLOUD_DOMAIN || "https://api-cloud.browserstack.com", + TEST_MANAGEMENT: + process.env.TEST_MGMT_DOMAIN || "https://test-management.browserstack.com", + API_OBSERVABILITY: + process.env.API_OBSERVABILITY_DOMAIN || + "https://api-observability.browserstack.com", + LIVE: process.env.LIVE_DOMAIN || "https://live.browserstack.com", + WWW: process.env.WWW_DOMAIN || "https://www.browserstack.com", + APP_LIVE: process.env.APP_LIVE_DOMAIN || "https://app-live.browserstack.com", + API_ACCESSIBILITY: + process.env.API_ACCESSIBILITY_DOMAIN || + "https://api-accessibility.browserstack.com", + SCANNER: process.env.SCANNER_DOMAIN || "https://scanner.browserstack.com", +} as const; diff --git a/src/lib/instrumentation.ts b/src/lib/instrumentation.ts index e6eccb4..f8b0024 100644 --- a/src/lib/instrumentation.ts +++ b/src/lib/instrumentation.ts @@ -1,6 +1,7 @@ import logger from "../logger.js"; import config from "../config.js"; import { createRequire } from "module"; +import { DOMAINS } from "./domains.js"; const require = createRequire(import.meta.url); const packageJson = require("../../package.json"); import axios from "axios"; @@ -27,7 +28,7 @@ export function trackMCP( return; } - const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event"; + const instrumentationEndpoint = `${DOMAINS.API}/sdk/v1/event`; const isSuccess = !error; const mcpClient = clientInfo?.name || "unknown"; diff --git a/src/logger.ts b/src/logger.ts index 8020128..0c65532 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,38 +1,24 @@ import { pino } from "pino"; -let logger: pino.Logger; - -if (process.env.NODE_ENV === "development") { - logger = pino({ - level: "debug", - transport: { - targets: [ - { - level: "debug", - target: "pino-pretty", - options: { - colorize: true, - levelFirst: true, - destination: - process.platform === "win32" - ? "C:\\Windows\\Temp\\browserstack-mcp-server.log" - : "/tmp/browserstack-mcp-server.log", - }, +// Always use the full logger configuration regardless of environment +const logger: pino.Logger = pino({ + level: "trace", + transport: { + targets: [ + { + level: "trace", + target: "pino-pretty", + options: { + colorize: true, + levelFirst: true, + destination: + process.platform === "win32" + ? "C:\\Windows\\Temp\\browserstack-mcp-server.log" + : "/tmp/browserstack-mcp-server.log", }, - ], - }, - }); -} else { - // NULL logger - logger = pino({ - level: "info", - transport: { - target: "pino/file", - options: { - destination: process.platform === "win32" ? "NUL" : "/dev/null", }, - }, - }); -} + ], + }, +}); export default logger; diff --git a/src/tools/accessibility.ts b/src/tools/accessibility.ts index 54b4647..76aab86 100644 --- a/src/tools/accessibility.ts +++ b/src/tools/accessibility.ts @@ -5,6 +5,7 @@ import { AccessibilityScanner } from "./accessiblity-utils/scanner.js"; import { AccessibilityReportFetcher } from "./accessiblity-utils/report-fetcher.js"; import { trackMCP } from "../lib/instrumentation.js"; import { parseAccessibilityReportFromCSV } from "./accessiblity-utils/report-parser.js"; +import { DOMAINS } from "../lib/domains.js"; const scanner = new AccessibilityScanner(); const reportFetcher = new AccessibilityReportFetcher(); @@ -37,7 +38,7 @@ async function runAccessibilityScan( content: [ { type: "text", - text: `❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`, + text: `❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [${DOMAINS.SCANNER}/site-scanner/scan-details/${name}].`, isError: true, }, ], @@ -55,7 +56,7 @@ async function runAccessibilityScan( content: [ { type: "text", - text: `✅ Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`, + text: `✅ Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [${DOMAINS.SCANNER}/site-scanner/scan-details/${name}].`, }, { type: "text", diff --git a/src/tools/accessiblity-utils/report-fetcher.ts b/src/tools/accessiblity-utils/report-fetcher.ts index 498ea20..ca4c51b 100644 --- a/src/tools/accessiblity-utils/report-fetcher.ts +++ b/src/tools/accessiblity-utils/report-fetcher.ts @@ -1,5 +1,6 @@ import axios from "axios"; import config from "../../config.js"; +import { DOMAINS } from "../../lib/domains.js"; interface ReportInitResponse { success: true; @@ -21,7 +22,7 @@ export class AccessibilityReportFetcher { async getReportLink(scanId: string, scanRunId: string): Promise { // Initiate CSV link generation - const initUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?scan_run_id=${scanRunId}`; + const initUrl = `${DOMAINS.API_ACCESSIBILITY}/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?scan_run_id=${scanRunId}`; const initResp = await axios.get(initUrl, { auth: this.auth, }); @@ -33,7 +34,7 @@ export class AccessibilityReportFetcher { const taskId = initResp.data.data.task_id; // Fetch the generated CSV link - const reportUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?task_id=${encodeURIComponent( + const reportUrl = `${DOMAINS.API_ACCESSIBILITY}/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?task_id=${encodeURIComponent( taskId, )}`; const reportResp = await axios.get(reportUrl, { diff --git a/src/tools/accessiblity-utils/scanner.ts b/src/tools/accessiblity-utils/scanner.ts index eb9a6da..17a3d61 100644 --- a/src/tools/accessiblity-utils/scanner.ts +++ b/src/tools/accessiblity-utils/scanner.ts @@ -6,6 +6,7 @@ import { ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js"; +import { DOMAINS } from "../../lib/domains.js"; export interface AccessibilityScanResponse { success: boolean; @@ -73,8 +74,8 @@ export class AccessibilityScanner { } try { - const { data } = await axios.post( - "https://api-accessibility.browserstack.com/api/website-scanner/v1/scans", + const { data } = await axios.post( + `${DOMAINS.API_ACCESSIBILITY}/api/website-scanner/v1/scans`, requestBody, { auth: this.auth }, ); @@ -99,7 +100,7 @@ export class AccessibilityScanner { ): Promise { try { const { data } = await axios.get( - `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/${scanRunId}/status`, + `${DOMAINS.API_ACCESSIBILITY}/api/website-scanner/v1/scans/${scanId}/scan_runs/${scanRunId}/status`, { auth: this.auth }, ); if (!data.success) diff --git a/src/tools/appautomate-utils/appautomate.ts b/src/tools/appautomate-utils/appautomate.ts index a52e622..c6b3d81 100644 --- a/src/tools/appautomate-utils/appautomate.ts +++ b/src/tools/appautomate-utils/appautomate.ts @@ -3,6 +3,7 @@ import axios from "axios"; import config from "../../config.js"; import FormData from "form-data"; import { customFuzzySearch } from "../../lib/fuzzy.js"; +import { DOMAINS } from "../../lib/domains.js"; interface Device { device: string; @@ -135,7 +136,7 @@ export async function uploadApp(appPath: string): Promise { formData.append("file", fs.createReadStream(filePath)); const response = await axios.post( - "https://api-cloud.browserstack.com/app-automate/upload", + `${DOMAINS.API_CLOUD}/app-automate/upload`, formData, { headers: { diff --git a/src/tools/applive-utils/start-session.ts b/src/tools/applive-utils/start-session.ts index 1b8af25..52edee4 100644 --- a/src/tools/applive-utils/start-session.ts +++ b/src/tools/applive-utils/start-session.ts @@ -9,6 +9,7 @@ import { uploadApp } from "./upload-app.js"; import { findDeviceByName } from "./device-search.js"; import { pickVersion } from "./version-utils.js"; import { DeviceEntry } from "./types.js"; +import { DOMAINS } from "../../lib/domains.js"; interface StartSessionArgs { appPath: string; @@ -76,7 +77,7 @@ export async function startSession(args: StartSessionArgs): Promise { speed: "1", start: "true", }); - const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`; + const launchUrl = `${DOMAINS.APP_LIVE}/dashboard#${params.toString()}&device=${deviceParam}`; openBrowser(launchUrl); return launchUrl + note; diff --git a/src/tools/applive-utils/upload-app.ts b/src/tools/applive-utils/upload-app.ts index a6dc756..9b8be0c 100644 --- a/src/tools/applive-utils/upload-app.ts +++ b/src/tools/applive-utils/upload-app.ts @@ -2,7 +2,7 @@ import axios, { AxiosError } from "axios"; import FormData from "form-data"; import fs from "fs"; import config from "../../config.js"; - +import { DOMAINS } from "../../lib/domains.js"; interface UploadResponse { app_url: string; } @@ -17,7 +17,7 @@ export async function uploadApp(filePath: string): Promise { try { const response = await axios.post( - "https://api-cloud.browserstack.com/app-live/upload", + `${DOMAINS.API_CLOUD}/app-live/upload`, formData, { headers: { diff --git a/src/tools/automate-utils/fetch-screenshots.ts b/src/tools/automate-utils/fetch-screenshots.ts index eabbc7a..100190d 100644 --- a/src/tools/automate-utils/fetch-screenshots.ts +++ b/src/tools/automate-utils/fetch-screenshots.ts @@ -1,6 +1,7 @@ import config from "../../config.js"; import { assertOkResponse, maybeCompressBase64 } from "../../lib/utils.js"; import { SessionType } from "../../lib/constants.js"; +import { DOMAINS } from "../../lib/domains.js"; //Extracts screenshot URLs from BrowserStack session logs async function extractScreenshotUrls( @@ -10,7 +11,7 @@ async function extractScreenshotUrls( const credentials = `${config.browserstackUsername}:${config.browserstackAccessKey}`; const auth = Buffer.from(credentials).toString("base64"); - const baseUrl = `https://api.browserstack.com/${sessionType === SessionType.Automate ? "automate" : "app-automate"}`; + const baseUrl = `${DOMAINS.API}/${sessionType === SessionType.Automate ? "automate" : "app-automate"}`; const url = `${baseUrl}/sessions/${sessionId}/logs`; const response = await fetch(url, { diff --git a/src/tools/failurelogs-utils/app-automate.ts b/src/tools/failurelogs-utils/app-automate.ts index 7aff74a..5da9d92 100644 --- a/src/tools/failurelogs-utils/app-automate.ts +++ b/src/tools/failurelogs-utils/app-automate.ts @@ -1,5 +1,6 @@ import config from "../../config.js"; import { filterLinesByKeywords, validateLogResponse } from "./utils.js"; +import { DOMAINS } from "../../lib/domains.js"; const auth = Buffer.from( `${config.browserstackUsername}:${config.browserstackAccessKey}`, @@ -10,7 +11,7 @@ export async function retrieveDeviceLogs( sessionId: string, buildId: string, ): Promise { - const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`; + const url = `${DOMAINS.API}/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`; const response = await fetch(url, { headers: { @@ -34,7 +35,7 @@ export async function retrieveAppiumLogs( sessionId: string, buildId: string, ): Promise { - const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`; + const url = `${DOMAINS.API}/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`; const response = await fetch(url, { headers: { @@ -58,7 +59,7 @@ export async function retrieveCrashLogs( sessionId: string, buildId: string, ): Promise { - const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`; + const url = `${DOMAINS.API}/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`; const response = await fetch(url, { headers: { diff --git a/src/tools/failurelogs-utils/automate.ts b/src/tools/failurelogs-utils/automate.ts index e8b5e95..2e95af8 100644 --- a/src/tools/failurelogs-utils/automate.ts +++ b/src/tools/failurelogs-utils/automate.ts @@ -5,6 +5,7 @@ import { filterLinesByKeywords, validateLogResponse, } from "./utils.js"; +import { DOMAINS } from "../../lib/domains.js"; const auth = Buffer.from( `${config.browserstackUsername}:${config.browserstackAccessKey}`, @@ -14,7 +15,7 @@ const auth = Buffer.from( export async function retrieveNetworkFailures( sessionId: string, ): Promise { - const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`; + const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/networklogs`; const response = await fetch(url, { method: "GET", @@ -62,7 +63,7 @@ export async function retrieveNetworkFailures( export async function retrieveSessionFailures( sessionId: string, ): Promise { - const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`; + const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/logs`; const response = await fetch(url, { headers: { @@ -85,7 +86,7 @@ export async function retrieveSessionFailures( export async function retrieveConsoleFailures( sessionId: string, ): Promise { - const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`; + const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/consolelogs`; const response = await fetch(url, { headers: { diff --git a/src/tools/live-utils/start-session.ts b/src/tools/live-utils/start-session.ts index 1efb776..1b47e5a 100644 --- a/src/tools/live-utils/start-session.ts +++ b/src/tools/live-utils/start-session.ts @@ -2,6 +2,7 @@ import logger from "../../logger.js"; import childProcess from "child_process"; import { filterDesktop } from "./desktop-filter.js"; import { filterMobile } from "./mobile-filter.js"; +import { DOMAINS } from "../../lib/domains.js"; import { DesktopSearchArgs, MobileSearchArgs, @@ -71,7 +72,7 @@ function buildDesktopUrl( local: isLocal ? "true" : "false", start: "true", }); - return `https://live.browserstack.com/dashboard#${params.toString()}`; + return `${DOMAINS.LIVE}/dashboard#${params.toString()}`; } function buildMobileUrl( @@ -97,7 +98,7 @@ function buildMobileUrl( local: isLocal ? "true" : "false", start: "true", }); - return `https://live.browserstack.com/dashboard#${params.toString()}`; + return `${DOMAINS.LIVE}/dashboard#${params.toString()}`; } // ——— Open a browser window ——— diff --git a/src/tools/selfheal-utils/selfheal.ts b/src/tools/selfheal-utils/selfheal.ts index f9b837b..1834227 100644 --- a/src/tools/selfheal-utils/selfheal.ts +++ b/src/tools/selfheal-utils/selfheal.ts @@ -1,6 +1,6 @@ import { assertOkResponse } from "../../lib/utils.js"; import config from "../../config.js"; - +import { DOMAINS } from "../../lib/domains.js"; interface SelectorMapping { originalSelector: string; healedSelector: string; @@ -13,7 +13,7 @@ interface SelectorMapping { export async function getSelfHealSelectors(sessionId: string) { const credentials = `${config.browserstackUsername}:${config.browserstackAccessKey}`; const auth = Buffer.from(credentials).toString("base64"); - const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`; + const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/logs`; const response = await fetch(url, { headers: { diff --git a/src/tools/testmanagement-utils/TCG-utils/api.ts b/src/tools/testmanagement-utils/TCG-utils/api.ts index 7fd1d38..5a8a07f 100644 --- a/src/tools/testmanagement-utils/TCG-utils/api.ts +++ b/src/tools/testmanagement-utils/TCG-utils/api.ts @@ -13,6 +13,7 @@ import { } from "./types.js"; import { createTestCasePayload } from "./helpers.js"; import config from "../../../config.js"; +import { DOMAINS } from "../../../lib/domains.js"; /** * Fetch default and custom form fields for a project. @@ -46,7 +47,7 @@ export async function triggerTestCaseGeneration( folderId, projectId, source, - webhookUrl: `https://test-management.browserstack.com/api/v1/projects/${projectId}/folder/${folderId}/webhooks/tcg`, + webhookUrl: `${DOMAINS.TEST_MANAGEMENT}/api/v1/projects/${projectId}/folder/${folderId}/webhooks/tcg`, }, { headers: { @@ -343,7 +344,7 @@ export async function bulkCreateTestCases( export async function projectIdentifierToId( projectId: string, ): Promise { - const url = `https://test-management.browserstack.com/api/v1/projects/?q=${projectId}`; + const url = `${DOMAINS.TEST_MANAGEMENT}/api/v1/projects/?q=${projectId}`; const response = await axios.get(url, { headers: { diff --git a/src/tools/testmanagement-utils/TCG-utils/config.ts b/src/tools/testmanagement-utils/TCG-utils/config.ts index 4762e40..297f60a 100644 --- a/src/tools/testmanagement-utils/TCG-utils/config.ts +++ b/src/tools/testmanagement-utils/TCG-utils/config.ts @@ -1,10 +1,9 @@ -export const TCG_TRIGGER_URL = - "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/suggest-test-cases"; -export const TCG_POLL_URL = - "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/test-cases-polling"; -export const FETCH_DETAILS_URL = - "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/fetch-test-case-details"; +import { DOMAINS } from "../../../lib/domains.js"; + +export const TCG_TRIGGER_URL = `${DOMAINS.TEST_MANAGEMENT}/api/v1/integration/tcg/test-generation/suggest-test-cases`; +export const TCG_POLL_URL = `${DOMAINS.TEST_MANAGEMENT}/api/v1/integration/tcg/test-generation/test-cases-polling`; +export const FETCH_DETAILS_URL = `${DOMAINS.TEST_MANAGEMENT}/api/v1/integration/tcg/test-generation/fetch-test-case-details`; export const FORM_FIELDS_URL = (projectId: string): string => - `https://test-management.browserstack.com/api/v1/projects/${projectId}/form-fields-v2`; + `${DOMAINS.TEST_MANAGEMENT}/api/v1/projects/${projectId}/form-fields-v2`; export const BULK_CREATE_URL = (projectId: string, folderId: string): string => - `https://test-management.browserstack.com/api/v1/projects/${projectId}/folder/${folderId}/bulk-test-cases`; + `${DOMAINS.TEST_MANAGEMENT}/api/v1/projects/${projectId}/folder/${folderId}/bulk-test-cases`; diff --git a/src/tools/testmanagement-utils/add-test-result.ts b/src/tools/testmanagement-utils/add-test-result.ts index b8be68c..2ca82c3 100644 --- a/src/tools/testmanagement-utils/add-test-result.ts +++ b/src/tools/testmanagement-utils/add-test-result.ts @@ -3,7 +3,7 @@ import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; - +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for adding a test result to a test run. */ @@ -36,7 +36,7 @@ export async function addTestResult( ): Promise { try { const args = AddTestResultSchema.parse(rawArgs); - const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + const url = `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( args.project_identifier, )}/test-runs/${encodeURIComponent(args.test_run_id)}/results`; diff --git a/src/tools/testmanagement-utils/create-project-folder.ts b/src/tools/testmanagement-utils/create-project-folder.ts index ef83570..0ff685d 100644 --- a/src/tools/testmanagement-utils/create-project-folder.ts +++ b/src/tools/testmanagement-utils/create-project-folder.ts @@ -2,8 +2,9 @@ import axios from "axios"; import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { formatAxiosError } from "../../lib/error.js"; // or correct path +import { formatAxiosError } from "../../lib/error.js"; import { projectIdentifierToId } from "../testmanagement-utils/TCG-utils/api.js"; +import { DOMAINS } from "../../lib/domains.js"; // Schema for combined project/folder creation export const CreateProjFoldSchema = z.object({ @@ -59,7 +60,7 @@ export async function createProjectOrFolder( if (project_name) { try { const res = await axios.post( - "https://test-management.browserstack.com/api/v2/projects", + `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects`, { project: { name: project_name, description: project_description } }, { auth: { @@ -88,7 +89,7 @@ export async function createProjectOrFolder( throw new Error("Cannot create folder without project_identifier."); try { const res = await axios.post( - `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( projId, )}/folders`, { @@ -123,7 +124,7 @@ export async function createProjectOrFolder( - ID: ${folder.id} - Name: ${folder.name} - Project Identifier: ${projId} - Access it here: https://test-management.browserstack.com/projects/${projectId}/folder/${folder.id}/`, + Access it here: ${DOMAINS.TEST_MANAGEMENT}/projects/${projectId}/folder/${folder.id}/`, }, ], }; diff --git a/src/tools/testmanagement-utils/create-testcase.ts b/src/tools/testmanagement-utils/create-testcase.ts index bb9029a..67566fc 100644 --- a/src/tools/testmanagement-utils/create-testcase.ts +++ b/src/tools/testmanagement-utils/create-testcase.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; // or correct import { projectIdentifierToId } from "./TCG-utils/api.js"; +import { DOMAINS } from "../../lib/domains.js"; interface TestCaseStep { step: string; @@ -145,7 +146,7 @@ export async function createTestCase( try { const response = await axios.post( - `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( params.project_identifier, )}/folders/${encodeURIComponent(params.folder_id)}/test-cases`, body, @@ -185,7 +186,7 @@ export async function createTestCase( - Identifier: ${tc.identifier} - Title: ${tc.title} - You can view it here: https://test-management.browserstack.com/projects/${projectId}/folder/search?q=${tc.identifier}`, + You can view it here: ${DOMAINS.TEST_MANAGEMENT}/projects/${projectId}/folder/search?q=${tc.identifier}`, }, { type: "text", diff --git a/src/tools/testmanagement-utils/create-testrun.ts b/src/tools/testmanagement-utils/create-testrun.ts index 2b8b4c6..e181bf9 100644 --- a/src/tools/testmanagement-utils/create-testrun.ts +++ b/src/tools/testmanagement-utils/create-testrun.ts @@ -3,6 +3,7 @@ import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for creating a test run. @@ -64,7 +65,7 @@ export async function createTestRun( }; const args = CreateTestRunSchema.parse(inputArgs); - const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + const url = `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( args.project_identifier, )}/test-runs`; diff --git a/src/tools/testmanagement-utils/list-testcases.ts b/src/tools/testmanagement-utils/list-testcases.ts index f882830..9ecd815 100644 --- a/src/tools/testmanagement-utils/list-testcases.ts +++ b/src/tools/testmanagement-utils/list-testcases.ts @@ -3,6 +3,7 @@ import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for listing test cases with optional filters. @@ -47,7 +48,7 @@ export async function listTestCases( if (args.priority) params.append("priority", args.priority); if (args.p !== undefined) params.append("p", args.p.toString()); - const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + const url = `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( args.project_identifier, )}/test-cases?${params.toString()}`; diff --git a/src/tools/testmanagement-utils/list-testruns.ts b/src/tools/testmanagement-utils/list-testruns.ts index 5bad0bc..3f29474 100644 --- a/src/tools/testmanagement-utils/list-testruns.ts +++ b/src/tools/testmanagement-utils/list-testruns.ts @@ -3,6 +3,7 @@ import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for listing test runs with optional filters. @@ -34,7 +35,7 @@ export async function listTestRuns( } const url = - `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( args.project_identifier, )}/test-runs?` + params.toString(); diff --git a/src/tools/testmanagement-utils/update-testrun.ts b/src/tools/testmanagement-utils/update-testrun.ts index ed8c8d0..8a45208 100644 --- a/src/tools/testmanagement-utils/update-testrun.ts +++ b/src/tools/testmanagement-utils/update-testrun.ts @@ -3,6 +3,7 @@ import config from "../../config.js"; import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { formatAxiosError } from "../../lib/error.js"; +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for updating a test run with partial fields. @@ -38,7 +39,7 @@ export async function updateTestRun( ): Promise { try { const body = { test_run: args.test_run }; - const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent( + const url = `${DOMAINS.TEST_MANAGEMENT}/api/v2/projects/${encodeURIComponent( args.project_identifier, )}/test-runs/${encodeURIComponent(args.test_run_id)}/update`; diff --git a/src/tools/testmanagement-utils/upload-file.ts b/src/tools/testmanagement-utils/upload-file.ts index 3f76c2e..4c19b0d 100644 --- a/src/tools/testmanagement-utils/upload-file.ts +++ b/src/tools/testmanagement-utils/upload-file.ts @@ -8,6 +8,7 @@ import { v4 as uuidv4 } from "uuid"; import config from "../../config.js"; import { signedUrlMap } from "../../lib/inmemory-store.js"; import { projectIdentifierToId } from "./TCG-utils/api.js"; +import { DOMAINS } from "../../lib/domains.js"; /** * Schema for the upload file tool @@ -51,7 +52,7 @@ export async function uploadFile( const formData = new FormData(); formData.append("attachments[]", fs.createReadStream(file_path)); - const uploadUrl = `https://test-management.browserstack.com/api/v1/projects/${projectIdResponse}/generic/attachments/ai_uploads`; + const uploadUrl = `${DOMAINS.TEST_MANAGEMENT}/api/v1/projects/${projectIdResponse}/generic/attachments/ai_uploads`; const response = await axios.post(uploadUrl, formData, { headers: { From db40d5a4f5c032f374b42a6233685ad72819889a Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 2 Jun 2025 15:43:16 +0530 Subject: [PATCH 2/5] removing debugging data --- src/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index f255070..bfad2f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,6 @@ import addFailureLogsTools from "./tools/getFailureLogs.js"; import addAutomateTools from "./tools/automate.js"; import addSelfHealTools from "./tools/selfheal.js"; import { setupOnInitialized } from "./oninitialized.js"; -import { DOMAINS } from "./lib/domains.js"; function registerTools(server: McpServer) { addSDKTools(server); @@ -31,8 +30,6 @@ function registerTools(server: McpServer) { addSelfHealTools(server); } -console.log(DOMAINS.APP_LIVE); - // Create an MCP server const server: McpServer = new McpServer({ name: "BrowserStack MCP Server", From c7d4233ba2b9dd726e88f4ef752edd971627f4de Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 2 Jun 2025 15:45:52 +0530 Subject: [PATCH 3/5] refactor: restore environment-specific logger configuration --- src/logger.ts | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 0c65532..8020128 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,24 +1,38 @@ import { pino } from "pino"; -// Always use the full logger configuration regardless of environment -const logger: pino.Logger = pino({ - level: "trace", - transport: { - targets: [ - { - level: "trace", - target: "pino-pretty", - options: { - colorize: true, - levelFirst: true, - destination: - process.platform === "win32" - ? "C:\\Windows\\Temp\\browserstack-mcp-server.log" - : "/tmp/browserstack-mcp-server.log", +let logger: pino.Logger; + +if (process.env.NODE_ENV === "development") { + logger = pino({ + level: "debug", + transport: { + targets: [ + { + level: "debug", + target: "pino-pretty", + options: { + colorize: true, + levelFirst: true, + destination: + process.platform === "win32" + ? "C:\\Windows\\Temp\\browserstack-mcp-server.log" + : "/tmp/browserstack-mcp-server.log", + }, }, + ], + }, + }); +} else { + // NULL logger + logger = pino({ + level: "info", + transport: { + target: "pino/file", + options: { + destination: process.platform === "win32" ? "NUL" : "/dev/null", }, - ], - }, -}); + }, + }); +} export default logger; From 954b9ff8d76093d53b83001ff66469ecb3bac206 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 2 Jun 2025 15:50:51 +0530 Subject: [PATCH 4/5] fix: update API endpoints in accessibility and failure logs utilities --- src/tools/accessiblity-utils/scanner.ts | 2 +- src/tools/failurelogs-utils/automate.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/accessiblity-utils/scanner.ts b/src/tools/accessiblity-utils/scanner.ts index 17a3d61..d374134 100644 --- a/src/tools/accessiblity-utils/scanner.ts +++ b/src/tools/accessiblity-utils/scanner.ts @@ -74,7 +74,7 @@ export class AccessibilityScanner { } try { - const { data } = await axios.post( + const { data } = await axios.post( `${DOMAINS.API_ACCESSIBILITY}/api/website-scanner/v1/scans`, requestBody, { auth: this.auth }, diff --git a/src/tools/failurelogs-utils/automate.ts b/src/tools/failurelogs-utils/automate.ts index 2e95af8..415d303 100644 --- a/src/tools/failurelogs-utils/automate.ts +++ b/src/tools/failurelogs-utils/automate.ts @@ -15,7 +15,7 @@ const auth = Buffer.from( export async function retrieveNetworkFailures( sessionId: string, ): Promise { - const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/networklogs`; + const url = `${DOMAINS.API}/automate/sessions/${sessionId}/networklogs`; const response = await fetch(url, { method: "GET", @@ -63,7 +63,7 @@ export async function retrieveNetworkFailures( export async function retrieveSessionFailures( sessionId: string, ): Promise { - const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/logs`; + const url = `${DOMAINS.API}/automate/sessions/${sessionId}/logs`; const response = await fetch(url, { headers: { @@ -86,7 +86,7 @@ export async function retrieveSessionFailures( export async function retrieveConsoleFailures( sessionId: string, ): Promise { - const url = `${DOMAINS.API_CLOUD}/automate/sessions/${sessionId}/consolelogs`; + const url = `${DOMAINS.API}/automate/sessions/${sessionId}/consolelogs`; const response = await fetch(url, { headers: { From b3609e96916d09b4757a226235c9da28c49a30ba Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Wed, 4 Jun 2025 17:26:56 +0530 Subject: [PATCH 5/5] Update environment variable names --- src/lib/domains.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/domains.ts b/src/lib/domains.ts index fa946c3..8584bf9 100644 --- a/src/lib/domains.ts +++ b/src/lib/domains.ts @@ -1,17 +1,17 @@ export const DOMAINS = { - API: process.env.API_DOMAIN || "https://api.browserstack.com", + API: process.env.BROWSERSTACK_API_DOMAIN || "https://api.browserstack.com", API_CLOUD: - process.env.API_CLOUD_DOMAIN || "https://api-cloud.browserstack.com", + process.env.BROWSERSTACK_API_CLOUD_DOMAIN || "https://api-cloud.browserstack.com", TEST_MANAGEMENT: - process.env.TEST_MGMT_DOMAIN || "https://test-management.browserstack.com", + process.env.BROWSERSTACK_TEST_MGMT_DOMAIN || "https://test-management.browserstack.com", API_OBSERVABILITY: - process.env.API_OBSERVABILITY_DOMAIN || + process.env.BROWSERSTACK_API_OBSERVABILITY_DOMAIN || "https://api-observability.browserstack.com", - LIVE: process.env.LIVE_DOMAIN || "https://live.browserstack.com", - WWW: process.env.WWW_DOMAIN || "https://www.browserstack.com", - APP_LIVE: process.env.APP_LIVE_DOMAIN || "https://app-live.browserstack.com", + LIVE: process.env.BROWSERSTACK_LIVE_DOMAIN || "https://live.browserstack.com", + WWW: process.env.BROWSERSTACK_WWW_DOMAIN || "https://www.browserstack.com", + APP_LIVE: process.env.BROWSERSTACK_APP_LIVE_DOMAIN || "https://app-live.browserstack.com", API_ACCESSIBILITY: - process.env.API_ACCESSIBILITY_DOMAIN || + process.env.BROWSERSTACK_API_ACCESSIBILITY_DOMAIN || "https://api-accessibility.browserstack.com", - SCANNER: process.env.SCANNER_DOMAIN || "https://scanner.browserstack.com", + SCANNER: process.env.BROWSERSTACK_SCANNER_DOMAIN || "https://scanner.browserstack.com", } as const;