Skip to content

Commit 7bc7546

Browse files
manoj-k04claude
andcommitted
refactor(percy): drop unused fetchPercyToken calls in setup paths
The token was fetched server-side then discarded (replaced with placeholder instructions). Removing the dead fetch in runPercyScan and setUpPercyHandler eliminates a wasted API call per setup, drops the void percyToken; smell from the SDK handlers, and removes the last code path that sent privileged credentials across the function boundary just to throw them away. fetchPercyToken stays for fetchPercyChanges, which still uses it for legitimate server-side calls. Setup instructions now point users at .env or shell export. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3342751 commit 7bc7546

6 files changed

Lines changed: 17 additions & 128 deletions

File tree

src/tools/run-percy-scan.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
22
import { PercyIntegrationTypeEnum } from "./sdk-utils/common/types.js";
33
import { BrowserStackConfig } from "../lib/types.js";
4-
import { getBrowserStackAuth } from "../lib/get-auth.js";
5-
import { fetchPercyToken } from "./sdk-utils/percy-web/fetchPercyToken.js";
64
import { storedPercyResults } from "../lib/inmemory-store.js";
75
import {
86
getFrameworkTestCommand,
@@ -16,13 +14,9 @@ export async function runPercyScan(
1614
integrationType: PercyIntegrationTypeEnum;
1715
instruction?: string;
1816
},
19-
config: BrowserStackConfig,
17+
_config: BrowserStackConfig,
2018
): Promise<CallToolResult> {
21-
const { projectName, integrationType, instruction } = args;
22-
const authorization = getBrowserStackAuth(config);
23-
const percyToken = await fetchPercyToken(projectName, authorization, {
24-
type: integrationType,
25-
});
19+
const { projectName, instruction } = args;
2620

2721
// Check if we have stored data and project matches
2822
const stored = storedPercyResults.get();
@@ -31,8 +25,6 @@ export async function runPercyScan(
3125
const hasUpdatedFiles = checkForUpdatedFiles(stored, projectName);
3226
const updatedFiles = hasUpdatedFiles ? getUpdatedFiles(stored) : [];
3327

34-
void percyToken;
35-
3628
// Build steps array with conditional spread
3729
const steps = [
3830
generatePercyTokenInstructions(),
@@ -58,11 +50,13 @@ export async function runPercyScan(
5850
}
5951

6052
function generatePercyTokenInstructions(): string {
61-
return `Set the PERCY_TOKEN environment variable for your project. Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token), then export it locally — do not paste it into chat or commit it:
53+
return `Set the PERCY_TOKEN environment variable for your project. Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token) and add it to your project's .env file (PERCY_TOKEN=<your Percy project token>) or export it in your shell:
6254
6355
- macOS/Linux: export PERCY_TOKEN="<your Percy project token>"
6456
- Windows (PS): $env:PERCY_TOKEN="<your Percy project token>"
65-
- Windows (CMD): set PERCY_TOKEN=<your Percy project token>`;
57+
- Windows (CMD): set PERCY_TOKEN=<your Percy project token>
58+
59+
Do not paste the token into chat or commit it.`;
6660
}
6761

6862
const toAbs = (p: string): string | undefined =>

src/tools/sdk-utils/handler.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { formatToolResult } from "./common/utils.js";
22
import { BrowserStackConfig } from "../../lib/types.js";
33
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import { PercyIntegrationTypeEnum } from "./common/types.js";
5-
import { getBrowserStackAuth } from "../../lib/get-auth.js";
6-
import { fetchPercyToken } from "./percy-web/fetchPercyToken.js";
75
import { runPercyWeb } from "./percy-web/handler.js";
86
import { runPercyAutomateOnly } from "./percy-automate/handler.js";
97
import { runBstackSDKOnly } from "./bstack/sdkHandler.js";
@@ -60,8 +58,6 @@ export async function setUpPercyHandler(
6058
testFiles: {},
6159
});
6260

63-
const authorization = getBrowserStackAuth(config);
64-
6561
const folderPaths = input.folderPaths || [];
6662
const filePaths = input.filePaths || [];
6763

@@ -86,14 +82,7 @@ export async function setUpPercyHandler(
8682
);
8783
}
8884

89-
// Fetch the Percy token
90-
const percyToken = await fetchPercyToken(
91-
input.projectName,
92-
authorization,
93-
{ type: PercyIntegrationTypeEnum.WEB },
94-
);
95-
96-
const result = runPercyWeb(percyInput, percyToken);
85+
const result = runPercyWeb(percyInput);
9786
return await formatToolResult(result, "percy-web");
9887
} else if (input.integrationType === PercyIntegrationTypeEnum.AUTOMATE) {
9988
// First try Percy with BrowserStack SDK
@@ -142,15 +131,7 @@ export async function setUpPercyHandler(
142131
};
143132
const sdkResult = await runBstackSDKOnly(sdkInput, config, true);
144133
// Percy Automate instructions
145-
const percyToken = await fetchPercyToken(
146-
input.projectName,
147-
authorization,
148-
{ type: PercyIntegrationTypeEnum.AUTOMATE },
149-
);
150-
const percyAutomateResult = runPercyAutomateOnly(
151-
percyInput,
152-
percyToken,
153-
);
134+
const percyAutomateResult = runPercyAutomateOnly(percyInput);
154135

155136
// Combine steps: warning, SDK steps, Percy Automate steps
156137
const steps = [

src/tools/sdk-utils/percy-automate/handler.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ import { SDKSupportedLanguage } from "../common/types.js";
55

66
export function runPercyAutomateOnly(
77
input: SetUpPercyInput,
8-
percyToken: string,
98
): RunTestsInstructionResult {
109
const steps: RunTestsStep[] = [];
1110

12-
void percyToken;
13-
1411
// Assume configuration is supported due to guardrails at orchestration layer
1512
const languageConfig =
1613
SUPPORTED_CONFIGURATIONS[input.detectedLanguage as SDKSupportedLanguage];
@@ -28,7 +25,7 @@ export function runPercyAutomateOnly(
2825
steps.push({
2926
type: "instruction",
3027
title: "Set Percy Token in Environment",
31-
content: `Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token), then set PERCY_TOKEN in your environment (e.g. export PERCY_TOKEN="<your Percy project token>"). Do not paste the token into chat or commit it.`,
28+
content: `Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token) and add it to your project's .env file (PERCY_TOKEN=<your Percy project token>) or export it in your shell (e.g. export PERCY_TOKEN="<your Percy project token>"). Do not paste the token into chat or commit it.`,
3229
});
3330

3431
steps.push({

src/tools/sdk-utils/percy-web/handler.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ export let percyWebSetupInstructions = "";
1212

1313
export function runPercyWeb(
1414
input: SetUpPercyInput,
15-
percyToken: string,
1615
): RunTestsInstructionResult {
1716
const steps: RunTestsStep[] = [];
1817

19-
void percyToken;
20-
2118
// Assume configuration is supported due to guardrails at orchestration layer
2219
const languageConfig =
2320
SUPPORTED_CONFIGURATIONS[input.detectedLanguage as SDKSupportedLanguage];
@@ -34,10 +31,12 @@ export function runPercyWeb(
3431
steps.push({
3532
type: "instruction",
3633
title: "Set Percy Token in Environment",
37-
content: `Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token), then set it locally:
34+
content: `Retrieve your project's token from the Percy dashboard (https://percy.io → Project Settings → Project Token) and add it to your project's .env file (PERCY_TOKEN=<your Percy project token>) or export it in your shell:
3835
macOS/Linux: export PERCY_TOKEN="<your Percy project token>"
3936
Windows (PS): $env:PERCY_TOKEN="<your Percy project token>"
40-
Windows (CMD): set PERCY_TOKEN=<your Percy project token>`,
37+
Windows (CMD): set PERCY_TOKEN=<your Percy project token>
38+
39+
Do not paste the token into chat or commit it.`,
4140
});
4241

4342
steps.push({

tests/tools/percyTokenLeak.test.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.

tests/tools/runPercyScan.test.ts

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
import { describe, it, expect, vi, beforeEach, Mock } from "vitest";
22
import { runPercyScan } from "../../src/tools/run-percy-scan";
3-
import { fetchPercyToken } from "../../src/tools/sdk-utils/percy-web/fetchPercyToken";
43
import { storedPercyResults } from "../../src/lib/inmemory-store";
54
import { PercyIntegrationTypeEnum } from "../../src/tools/sdk-utils/common/types";
65

7-
vi.mock("../../src/lib/get-auth", () => ({
8-
getBrowserStackAuth: vi.fn().mockReturnValue("fake-user:fake-key"),
9-
}));
10-
vi.mock("../../src/tools/sdk-utils/percy-web/fetchPercyToken", () => ({
11-
fetchPercyToken: vi.fn(),
12-
}));
136
vi.mock("../../src/lib/inmemory-store", () => ({
147
storedPercyResults: { get: vi.fn(), set: vi.fn() },
158
}));
@@ -29,9 +22,7 @@ const mockConfig = {
2922
describe("runPercyScan", () => {
3023
beforeEach(() => vi.clearAllMocks());
3124

32-
it("SECURITY: never echoes the fetched Percy token in output", async () => {
33-
const SECRET = "percy-secret-token-DO-NOT-LEAK";
34-
(fetchPercyToken as Mock).mockResolvedValue(SECRET);
25+
it("renders PERCY_TOKEN setup instructions with placeholder", async () => {
3526
(storedPercyResults.get as Mock).mockReturnValue(null);
3627

3728
const result = await runPercyScan(
@@ -43,14 +34,12 @@ describe("runPercyScan", () => {
4334
);
4435

4536
const text = result.content[0].text as string;
46-
expect(text).not.toContain(SECRET);
4737
expect(text).toContain("PERCY_TOKEN");
4838
expect(text).toContain("<your Percy project token>");
39+
expect(text).toContain(".env");
4940
});
5041

51-
it("SUCCESS: includes updated file instructions when available", async () => {
52-
const SECRET = "percy-secret-token-DO-NOT-LEAK";
53-
(fetchPercyToken as Mock).mockResolvedValue(SECRET);
42+
it("includes updated file instructions when available", async () => {
5443
(storedPercyResults.get as Mock).mockReturnValue({
5544
projectName: "my-project",
5645
testFiles: { "/tests/login.test.js": true },
@@ -67,13 +56,10 @@ describe("runPercyScan", () => {
6756
);
6857

6958
const text = result.content[0].text as string;
70-
expect(text).not.toContain(SECRET);
7159
expect(text).toContain("Updated files to run");
7260
});
7361

74-
it("SUCCESS: includes custom instruction steps", async () => {
75-
const SECRET = "percy-secret-token-DO-NOT-LEAK";
76-
(fetchPercyToken as Mock).mockResolvedValue(SECRET);
62+
it("includes custom instruction steps", async () => {
7763
(storedPercyResults.get as Mock).mockReturnValue(null);
7864

7965
const result = await runPercyScan(
@@ -86,24 +72,6 @@ describe("runPercyScan", () => {
8672
);
8773

8874
const text = result.content[0].text as string;
89-
expect(text).not.toContain(SECRET);
9075
expect(text).toContain("npx percy exec");
9176
});
92-
93-
it("FAIL: throws when Percy token fetch fails", async () => {
94-
(fetchPercyToken as Mock).mockRejectedValue(
95-
new Error("Percy token not found"),
96-
);
97-
(storedPercyResults.get as Mock).mockReturnValue(null);
98-
99-
await expect(
100-
runPercyScan(
101-
{
102-
projectName: "bad-project",
103-
integrationType: PercyIntegrationTypeEnum.WEB,
104-
},
105-
mockConfig,
106-
),
107-
).rejects.toThrow("Percy token not found");
108-
});
10977
});

0 commit comments

Comments
 (0)