Skip to content

Refactoring SDK Tool #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b589b4e
Refactoring SDK Tool
tech-sushant Jul 22, 2025
7f0bd97
add projectName parameter to BrowserStack YML instructions
tech-sushant Jul 23, 2025
d2f7a6d
Percy changes
tech-sushant Jul 28, 2025
395153c
Add framework compatibility checks for Percy integration types
tech-sushant Jul 28, 2025
ccf16a9
Refactor Percy handler functions to remove unsupported configuration …
tech-sushant Jul 28, 2025
c2e6d82
Update tool descriptions and registration names for BrowserStack and …
tech-sushant Jul 28, 2025
6bc96cd
Update Percy integration type and adjust related documentation for Au…
tech-sushant Jul 30, 2025
c67e857
Refactor BrowserStack and Percy integration tools for improved setup …
tech-sushant Jul 30, 2025
0d5b48b
Merge branch 'main' into bstack-sdk
tech-sushant Jul 30, 2025
38fd222
Update command for running Percy Automate with BrowserStack and Pytest
tech-sushant Jul 30, 2025
0c84983
Merge branch 'bstack-sdk' of https://github.com/tech-sushant/mcp-serv…
tech-sushant Jul 30, 2025
7132f0a
Add Percy tools integration and support checks for BrowserStack
tech-sushant Jul 30, 2025
f1456b8
Enhance BrowserStack SDK integration
tech-sushant Jul 30, 2025
c7c5c56
Refactor Percy token handling
tech-sushant Jul 30, 2025
ef7a337
linting
tech-sushant Jul 30, 2025
c5b07c5
Remove config logging from bootstrap failure message and delete unuse…
tech-sushant Jul 31, 2025
970d3ec
Remove integrationType from SetUpPercyParamsShape and update support …
tech-sushant Jul 31, 2025
8e29b42
Update SETUP_PERCY_DESCRIPTION for clarity on tool usage and integration
tech-sushant Jul 31, 2025
6185e9e
Refactor BrowserStack and Percy integration descriptions for clarity …
tech-sushant Aug 1, 2025
c80f25c
Percy changes
tech-sushant Aug 1, 2025
5483db3
Refactor Percy and BrowserStack integration
tech-sushant Aug 1, 2025
da46709
Enhance Percy integration documentation and support for additional fr…
tech-sushant Aug 3, 2025
99080dc
Refactor Percy Automate configuration to support multiple drivers and…
tech-sushant Aug 4, 2025
be274b1
Update screenshot method in tests and enhance
tech-sushant Aug 4, 2025
164e993
Add instructions for iterative Percy integration in test files
tech-sushant Aug 4, 2025
5cb17b5
refactoring
tech-sushant Aug 7, 2025
4267079
refine ++
tech-sushant Aug 7, 2025
596c18f
refactoring ++
tech-sushant Aug 7, 2025
a69309c
Refactor fetchPercyToken function to implement token retrieval logic …
tech-sushant Aug 8, 2025
85b74cc
Error handling
tech-sushant Aug 8, 2025
a01a9bd
Linting ++
tech-sushant Aug 11, 2025
d485f39
Improvements ++
tech-sushant Aug 12, 2025
76f8fe9
Modularity ++
tech-sushant Aug 12, 2025
f00a5b7
Merge branch 'main' into bstack-sdk
tech-sushant Aug 12, 2025
9a67c26
Merge branch 'main' into bstack-sdk
tech-sushant Aug 12, 2025
0a6481c
Add snapshot instructions to configuration mappings for various langu…
tech-sushant Aug 12, 2025
c6fdc33
Additional step for percy snapshot commands
tech-sushant Aug 12, 2025
e0fcc1f
Update ++
tech-sushant Aug 13, 2025
bebfd39
Merge branch 'main' of https://github.com/tech-sushant/mcp-server int…
tech-sushant Aug 13, 2025
a09fb0e
Update src/tools/sdk-utils/percy-automate/constants.ts
tech-sushant Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/inmemory-store.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const signedUrlMap = new Map<string, object>();
export const testFilePathsMap = new Map<string, string[]>();
8 changes: 8 additions & 0 deletions src/server-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
import logger from "./logger.js";
import addSDKTools from "./tools/bstack-sdk.js";
import addPercyTools from "./tools/percy-sdk.js";
import addSimulatePercyChangeTool from "./tools/simulate-percy.js";
import addBrowserLiveTools from "./tools/live.js";
import addAccessibilityTools from "./tools/accessibility.js";
import addTestManagementTools from "./tools/testmanagement.js";
Expand All @@ -15,6 +17,8 @@ import addFailureLogsTools from "./tools/get-failure-logs.js";
import addAutomateTools from "./tools/automate.js";
import addSelfHealTools from "./tools/selfheal.js";
import addAppLiveTools from "./tools/applive.js";
import addListTestFilesTool from "./tools/list-test-files.js";
import addPercySnapshotTools from "./tools/add-percy-snapshots.js";
import { setupOnInitialized } from "./oninitialized.js";
import { BrowserStackConfig } from "./lib/types.js";

Expand Down Expand Up @@ -48,13 +52,17 @@ export class BrowserStackMcpServer {
const toolAdders = [
addAccessibilityTools,
addSDKTools,
addPercyTools,
addSimulatePercyChangeTool,
addAppLiveTools,
addBrowserLiveTools,
addTestManagementTools,
addAppAutomationTools,
addFailureLogsTools,
addAutomateTools,
addSelfHealTools,
addListTestFilesTool,
addPercySnapshotTools,
];

toolAdders.forEach((adder) => {
Expand Down
48 changes: 48 additions & 0 deletions src/tools/add-percy-snapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { testFilePathsMap } from "../lib/inmemory-store.js";
import { UpdateTestFileWithInstructionsParams } from "./percy-snapshot-utils/constants.js";
import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { percyWebSetupInstructions } from "../tools/sdk-utils/percy-web/handler.js";

async function updateTestsWithPercyCommands(args: {
uuid: string;
index: number;
}): Promise<CallToolResult> {
const { uuid, index } = args;
const filePaths = testFilePathsMap.get(uuid);

if (!filePaths) {
throw new Error(`No test files found in memory for UUID: ${uuid}`);
}
if (index < 0 || index >= filePaths.length) {
throw new Error(
`Invalid index: ${index}. There are ${filePaths.length} files for UUID: ${uuid}`,
);
}
const result = await updateFileAndStep(
filePaths[index],
index,
filePaths.length,
percyWebSetupInstructions,
);

return {
content: result,
};
}

export default function addPercySnapshotTools(server: McpServer) {
const tools: Record<string, any> = {};

tools.addPercySnapshotCommands = server.tool(
"addPercySnapshotCommands",
"Adds Percy snapshot commands to the specified test files.",
UpdateTestFileWithInstructionsParams,
async (args) => {
return await updateTestsWithPercyCommands(args);
},
);

return tools;
}
247 changes: 9 additions & 238 deletions src/tools/bstack-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,254 +1,25 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { trackMCP } from "../lib/instrumentation.js";
import { getSDKPrefixCommand } from "./sdk-utils/commands.js";

import {
SDKSupportedBrowserAutomationFramework,
SDKSupportedLanguage,
SDKSupportedTestingFramework,
SDKSupportedLanguageEnum,
SDKSupportedBrowserAutomationFrameworkEnum,
SDKSupportedTestingFrameworkEnum,
} from "./sdk-utils/types.js";

import {
generateBrowserStackYMLInstructions,
getInstructionsForProjectConfiguration,
formatInstructionsWithNumbers,
} from "./sdk-utils/instructions.js";

import {
formatPercyInstructions,
getPercyInstructions,
} from "./sdk-utils/percy/instructions.js";
import { getBrowserStackAuth } from "../lib/get-auth.js";
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";

/**
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
*/
export async function bootstrapProjectWithSDK({
detectedBrowserAutomationFramework,
detectedTestingFramework,
detectedLanguage,
desiredPlatforms,
enablePercy,
config,
}: {
detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework;
detectedTestingFramework: SDKSupportedTestingFramework;
detectedLanguage: SDKSupportedLanguage;
desiredPlatforms: string[];
enablePercy: boolean;
config: BrowserStackConfig;
}): Promise<CallToolResult> {
// Get credentials from config
const authString = getBrowserStackAuth(config);
const [username, accessKey] = authString.split(":");

// Handle frameworks with unique setup instructions that don't use browserstack.yml
if (
detectedBrowserAutomationFramework === "cypress" ||
detectedTestingFramework === "webdriverio"
) {
let combinedInstructions = getInstructionsForProjectConfiguration(
detectedBrowserAutomationFramework,
detectedTestingFramework,
detectedLanguage,
username,
accessKey,
);

if (enablePercy) {
const percyInstructions = getPercyInstructions(
detectedLanguage,
detectedBrowserAutomationFramework,
detectedTestingFramework,
);

if (percyInstructions) {
combinedInstructions +=
"\n\n" + formatPercyInstructions(percyInstructions);
} else {
throw new Error(
`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`,
);
}
}

// Apply consistent formatting for all configurations
return formatFinalInstructions(combinedInstructions);
}

// Handle default flow using browserstack.yml
const sdkSetupCommand = getSDKPrefixCommand(
detectedLanguage,
detectedTestingFramework,
username,
accessKey,
);

const ymlInstructions = generateBrowserStackYMLInstructions(
desiredPlatforms,
enablePercy,
);

const instructionsForProjectConfiguration =
getInstructionsForProjectConfiguration(
detectedBrowserAutomationFramework,
detectedTestingFramework,
detectedLanguage,
username,
accessKey,
);

let combinedInstructions = "";

// Step 1: Add SDK setup command
if (sdkSetupCommand) {
combinedInstructions += sdkSetupCommand;
}

// Step 2: Add browserstack.yml setup
if (ymlInstructions) {
combinedInstructions += "\n\n---STEP---\n" + ymlInstructions;
}

// Step 3: Add language/framework-specific setup
if (instructionsForProjectConfiguration) {
combinedInstructions += "\n\n" + instructionsForProjectConfiguration;
}

// Step 4: Add Percy setup if applicable
if (enablePercy) {
const percyInstructions = getPercyInstructions(
detectedLanguage,
detectedBrowserAutomationFramework,
detectedTestingFramework,
);

if (percyInstructions) {
combinedInstructions +=
"\n\n" + formatPercyInstructions(percyInstructions);
} else {
throw new Error(
`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`,
);
}
}

// Apply consistent formatting for all configurations
return formatFinalInstructions(combinedInstructions);
}

// Helper function to apply consistent formatting to all instruction types
function formatFinalInstructions(combinedInstructions: string): CallToolResult {
const fullInstructions = `⚠️ IMPORTANT: DO NOT SKIP ANY STEP
All the setup steps described in this file MUST be executed regardless of any existing configuration or setup.
This ensures proper BrowserStack SDK setup.

${formatInstructionsWithNumbers(combinedInstructions)}`;

return {
content: [
{
type: "text",
text: fullInstructions,
isError: false,
},
],
};
}

export default function addSDKTools(
export function registerRunBrowserStackTestsTool(
server: McpServer,
config: BrowserStackConfig,
) {
const tools: Record<string, any> = {};

tools.setupBrowserStackAutomateTests = server.tool(
"setupBrowserStackAutomateTests",
"Set up and run automated web-based tests on BrowserStack using the BrowserStack SDK. Use for functional or integration tests on BrowserStack, with optional Percy visual testing for supported frameworks. Example prompts: run this test on browserstack; run this test on browserstack with Percy; set up this project for browserstack with Percy. Integrate BrowserStack SDK into your project",
{
detectedBrowserAutomationFramework: z
.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
.describe(
"The automation framework configured in the project. Example: 'playwright', 'selenium'",
),

detectedTestingFramework: z
.nativeEnum(SDKSupportedTestingFrameworkEnum)
.describe(
"The testing framework used in the project. Be precise with framework selection Example: 'webdriverio', 'jest', 'pytest', 'junit4', 'junit5', 'mocha'",
),

detectedLanguage: z
.nativeEnum(SDKSupportedLanguageEnum)
.describe(
"The programming language used in the project. Example: 'nodejs', 'python', 'java', 'csharp'",
),

desiredPlatforms: z
.array(z.enum(["windows", "macos", "android", "ios"]))
.describe(
"The platforms the user wants to test on. Always ask this to the user, do not try to infer this.",
),

enablePercy: z
.boolean()
.optional()
.default(false)
.describe(
"Set to true if the user wants to enable Percy for visual testing. Defaults to false.",
),
},

RUN_ON_BROWSERSTACK_DESCRIPTION,
RunTestsOnBrowserStackParamsShape,
async (args) => {
try {
trackMCP(
"runTestsOnBrowserStack",
server.server.getClientVersion()!,
undefined,
config,
);

return await bootstrapProjectWithSDK({
detectedBrowserAutomationFramework:
args.detectedBrowserAutomationFramework as SDKSupportedBrowserAutomationFramework,

detectedTestingFramework:
args.detectedTestingFramework as SDKSupportedTestingFramework,

detectedLanguage: args.detectedLanguage as SDKSupportedLanguage,

desiredPlatforms: args.desiredPlatforms,
enablePercy: args.enablePercy,
config,
});
} catch (error) {
trackMCP(
"runTestsOnBrowserStack",
server.server.getClientVersion()!,
error,
config,
);

return {
content: [
{
type: "text",
text: `Failed to bootstrap project with BrowserStack SDK. Error: ${error}. Please open an issue on GitHub if the problem persists`,
isError: true,
},
],
isError: true,
};
}
return runTestsOnBrowserStackHandler(args, config);
},
);

return tools;
}

export default registerRunBrowserStackTestsTool;
Loading