Skip to content

Commit 819fe6d

Browse files
committed
Feat: Add failure tracking for MCP events across various tools
1 parent 34a07d9 commit 819fe6d

File tree

8 files changed

+184
-145
lines changed

8 files changed

+184
-145
lines changed

src/lib/instrumentation.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ interface MCPEventPayload {
99
mcp_version: string;
1010
tool_name: string;
1111
mcp_client: string;
12+
success?: boolean;
13+
error_message?: string;
14+
error_type?: string;
1215
};
1316
}
1417

@@ -33,6 +36,7 @@ export function trackMCPEvent(
3336
mcp_version: packageJson.version,
3437
tool_name: toolName,
3538
mcp_client: mcpClient,
39+
success: true,
3640
},
3741
};
3842

@@ -61,3 +65,54 @@ export function trackMCPEvent(
6165
);
6266
});
6367
}
68+
69+
export function trackMCPFailure(
70+
toolName: string,
71+
error: unknown,
72+
clientInfo: { name?: string; version?: string },
73+
): void {
74+
const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
75+
76+
const mcpClient = clientInfo?.name || "unknown";
77+
const errorMessage = error instanceof Error ? error.message : String(error);
78+
const errorType = error instanceof Error ? error.constructor.name : "Unknown";
79+
80+
logger.error(`Tool failure: ${toolName} - ${errorMessage}`, { errorType });
81+
82+
const event: MCPEventPayload = {
83+
event_type: "MCPInstrumentation",
84+
event_properties: {
85+
mcp_version: packageJson.version,
86+
tool_name: toolName,
87+
mcp_client: mcpClient,
88+
success: false,
89+
error_message: errorMessage,
90+
error_type: errorType,
91+
},
92+
};
93+
94+
axios
95+
.post(instrumentationEndpoint, event, {
96+
headers: {
97+
"Content-Type": "application/json",
98+
Authorization: `Basic ${Buffer.from(
99+
`${config.browserstackUsername}:${config.browserstackAccessKey}`,
100+
).toString("base64")}`,
101+
},
102+
timeout: 2000,
103+
})
104+
.then((response) => {
105+
logger.info("MCP failure event tracked successfully", {
106+
toolName,
107+
response,
108+
});
109+
})
110+
.catch((error: unknown) => {
111+
logger.warn(
112+
`Failed to track MCP failure event: ${error instanceof Error ? error.message : String(error)}`,
113+
{
114+
toolName,
115+
},
116+
);
117+
});
118+
}

src/tools/accessibility.ts

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,33 @@ import {
55
startAccessibilityScan,
66
AccessibilityScanResponse,
77
} from "./accessiblity-utils/accessibility";
8-
import { trackMCPEvent } from "../lib/instrumentation";
8+
import { trackMCPEvent, trackMCPFailure } from "../lib/instrumentation";
99

1010
async function runAccessibilityScan(
1111
name: string,
1212
pageURL: string,
1313
): Promise<CallToolResult> {
14-
try {
15-
const response: AccessibilityScanResponse = await startAccessibilityScan(
16-
name,
17-
[pageURL],
18-
);
19-
const scanId = response.data?.id;
20-
const scanRunId = response.data?.scanRunId;
21-
22-
if (!scanId || !scanRunId) {
23-
throw new Error(
24-
"Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists",
25-
);
26-
}
14+
const response: AccessibilityScanResponse = await startAccessibilityScan(
15+
name,
16+
[pageURL],
17+
);
18+
const scanId = response.data?.id;
19+
const scanRunId = response.data?.scanRunId;
2720

28-
return {
29-
content: [
30-
{
31-
type: "text",
32-
text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
33-
},
34-
],
35-
};
36-
} catch (error) {
37-
return {
38-
content: [
39-
{
40-
type: "text",
41-
text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
42-
isError: true,
43-
},
44-
],
45-
isError: true,
46-
};
21+
if (!scanId || !scanRunId) {
22+
throw new Error(
23+
"Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists",
24+
);
4725
}
26+
27+
return {
28+
content: [
29+
{
30+
type: "text",
31+
text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
32+
},
33+
],
34+
};
4835
}
4936

5037
export default function addAccessibilityTools(server: McpServer) {
@@ -56,9 +43,22 @@ export default function addAccessibilityTools(server: McpServer) {
5643
pageURL: z.string().describe("The URL to scan for accessibility issues"),
5744
},
5845
async (args) => {
59-
const clientInfo = server.server.getClientVersion();
60-
trackMCPEvent("startAccessibilityScan", clientInfo!);
61-
return runAccessibilityScan(args.name, args.pageURL);
46+
try {
47+
trackMCPEvent("startAccessibilityScan", server.server.getClientVersion()!);
48+
return await runAccessibilityScan(args.name, args.pageURL);
49+
} catch (error) {
50+
trackMCPFailure("startAccessibilityScan", error, server.server.getClientVersion()!);
51+
return {
52+
content: [
53+
{
54+
type: "text",
55+
text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
56+
isError: true,
57+
},
58+
],
59+
isError: true,
60+
};
61+
}
6262
},
6363
);
6464
}

src/tools/applive.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "fs";
55
import { startSession } from "./applive-utils/start-session";
66
import logger from "../logger";
7-
import { trackMCPEvent } from "../lib/instrumentation";
7+
import { trackMCPEvent, trackMCPFailure } from "../lib/instrumentation";
88

99
/**
1010
* Launches an App Live Session on BrowserStack.
@@ -34,16 +34,12 @@ export async function startAppLiveSession(args: {
3434
if (args.desiredPlatform === "ios" && !args.appPath.endsWith(".ipa")) {
3535
throw new Error("You must provide a valid iOS app path.");
3636
}
37+
3738
// check if the app path exists && is readable
38-
try {
39-
if (!fs.existsSync(args.appPath)) {
40-
throw new Error("The app path does not exist.");
41-
}
42-
fs.accessSync(args.appPath, fs.constants.R_OK);
43-
} catch (error) {
44-
logger.error("The app path does not exist or is not readable: %s", error);
45-
throw new Error("The app path does not exist or is not readable.");
39+
if (!fs.existsSync(args.appPath)) {
40+
throw new Error("The app path does not exist.");
4641
}
42+
fs.accessSync(args.appPath, fs.constants.R_OK);
4743

4844
const launchUrl = await startSession({
4945
appPath: args.appPath,
@@ -90,15 +86,16 @@ export default function addAppLiveTools(server: McpServer) {
9086
},
9187
async (args) => {
9288
try {
93-
const clientInfo = server.server.getClientVersion();
94-
trackMCPEvent("runAppLiveSession", clientInfo!);
95-
return startAppLiveSession(args);
89+
trackMCPEvent("runAppLiveSession", server.server.getClientVersion()!);
90+
return await startAppLiveSession(args);
9691
} catch (error) {
92+
logger.error("App live session failed: %s", error);
93+
trackMCPFailure("runAppLiveSession", error, server.server.getClientVersion()!);
9794
return {
9895
content: [
9996
{
10097
type: "text",
101-
text: `Failed to start an app live session. Error: ${error}. Please open an issue on GitHub if the problem persists`,
98+
text: `Failed to start app live session: ${error instanceof Error ? error.message : String(error)}`,
10299
isError: true,
103100
},
104101
],

src/tools/automate.ts

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from "zod";
33
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import logger from "../logger";
55
import { retrieveNetworkFailures } from "../lib/api";
6-
import { trackMCPEvent } from "../lib/instrumentation";
6+
import { trackMCPEvent, trackMCPFailure } from "../lib/instrumentation";
77

88
/**
99
* Fetches failed network requests from a BrowserStack Automate session.
@@ -12,43 +12,26 @@ import { trackMCPEvent } from "../lib/instrumentation";
1212
export async function getNetworkFailures(args: {
1313
sessionId: string;
1414
}): Promise<CallToolResult> {
15-
try {
16-
const failureLogs = await retrieveNetworkFailures(args.sessionId);
17-
logger.info(
18-
"Successfully fetched failure network logs for session: %s",
19-
args.sessionId,
20-
);
21-
22-
// Check if there are any failures
23-
const hasFailures = failureLogs.totalFailures > 0;
24-
const text = hasFailures
25-
? `${failureLogs.totalFailures} network failure(s) found for session :\n\n${JSON.stringify(failureLogs.failures, null, 2)}`
26-
: `No network failures found for session`;
15+
const failureLogs = await retrieveNetworkFailures(args.sessionId);
16+
logger.info(
17+
"Successfully fetched failure network logs for session: %s",
18+
args.sessionId,
19+
);
2720

28-
return {
29-
content: [
30-
{
31-
type: "text",
32-
text,
33-
},
34-
],
35-
};
36-
} catch (error) {
37-
const errorMessage =
38-
error instanceof Error ? error.message : "An unknown error occurred";
39-
logger.error("Failed to fetch network logs: %s", errorMessage);
21+
// Check if there are any failures
22+
const hasFailures = failureLogs.totalFailures > 0;
23+
const text = hasFailures
24+
? `${failureLogs.totalFailures} network failure(s) found for session :\n\n${JSON.stringify(failureLogs.failures, null, 2)}`
25+
: `No network failures found for session`;
4026

41-
return {
42-
content: [
43-
{
44-
type: "text",
45-
text: `Failed to fetch network logs: ${errorMessage}`,
46-
isError: true,
47-
},
48-
],
49-
isError: true,
50-
};
51-
}
27+
return {
28+
content: [
29+
{
30+
type: "text",
31+
text,
32+
},
33+
],
34+
};
5235
}
5336

5437
export default function addAutomateTools(server: McpServer) {
@@ -59,9 +42,26 @@ export default function addAutomateTools(server: McpServer) {
5942
sessionId: z.string().describe("The Automate session ID."),
6043
},
6144
async (args) => {
62-
const clientInfo = server.server.getClientVersion();
63-
trackMCPEvent("startAccessibilityScan", clientInfo!);
64-
return getNetworkFailures(args);
45+
try {
46+
trackMCPEvent("getNetworkFailures", server.server.getClientVersion()!);
47+
return await getNetworkFailures(args);
48+
} catch (error) {
49+
const errorMessage = error instanceof Error ? error.message : String(error);
50+
logger.error("Failed to fetch network logs: %s", errorMessage);
51+
52+
trackMCPFailure("getNetworkFailures", error, server.server.getClientVersion()!);
53+
54+
return {
55+
content: [
56+
{
57+
type: "text",
58+
text: `Failed to fetch network logs: ${errorMessage}`,
59+
isError: true,
60+
},
61+
],
62+
isError: true,
63+
};
64+
}
6565
},
6666
);
6767
}

src/tools/bstack-sdk.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
generateBrowserStackYMLInstructions,
1111
getInstructionsForProjectConfiguration,
1212
} from "./sdk-utils/instructions";
13-
import { trackMCPEvent } from "../lib/instrumentation";
13+
import { trackMCPEvent, trackMCPFailure } from "../lib/instrumentation";
1414

1515
/**
1616
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
@@ -73,23 +73,19 @@ export default function addSDKTools(server: McpServer) {
7373
),
7474
},
7575
async (args) => {
76-
const detectedBrowserAutomationFramework =
77-
args.detectedBrowserAutomationFramework as SDKSupportedBrowserAutomationFramework;
78-
const detectedTestingFramework =
79-
args.detectedTestingFramework as SDKSupportedTestingFramework;
80-
const detectedLanguage = args.detectedLanguage as SDKSupportedLanguage;
81-
const desiredPlatforms = args.desiredPlatforms;
82-
8376
try {
84-
const clientInfo = server.server.getClientVersion();
85-
trackMCPEvent("runTestsOnBrowserStack", clientInfo!);
86-
return bootstrapProjectWithSDK({
87-
detectedBrowserAutomationFramework,
88-
detectedTestingFramework,
89-
detectedLanguage,
90-
desiredPlatforms,
77+
trackMCPEvent("runTestsOnBrowserStack", server.server.getClientVersion()!);
78+
79+
return await bootstrapProjectWithSDK({
80+
detectedBrowserAutomationFramework:
81+
args.detectedBrowserAutomationFramework as SDKSupportedBrowserAutomationFramework,
82+
detectedTestingFramework:
83+
args.detectedTestingFramework as SDKSupportedTestingFramework,
84+
detectedLanguage: args.detectedLanguage as SDKSupportedLanguage,
85+
desiredPlatforms: args.desiredPlatforms,
9186
});
9287
} catch (error) {
88+
trackMCPFailure("runTestsOnBrowserStack", error, server.server.getClientVersion()!);
9389
return {
9490
content: [
9591
{

0 commit comments

Comments
 (0)