@browserstack/mcp-server 1.4.1-beta.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { apiClient } from "../../../lib/apiClient.js";
2
- import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, TC_DETAILS_MAX_BATCH, } from "./config.js";
2
+ import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, TC_DETAILS_MAX_BATCH, TCG_POLL_INTERVAL_MS, TCG_POLL_MAX_WAIT_MS, } from "./config.js";
3
3
  import { createTestCasePayload } from "./helpers.js";
4
4
  import { getBrowserStackAuth } from "../../../lib/get-auth.js";
5
5
  import { getTMBaseURL } from "../../../lib/tm-base-url.js";
@@ -118,9 +118,15 @@ export async function pollTestCaseDetails(traceRequestId, config) {
118
118
  let done = false;
119
119
  const tmBaseUrl = await getTMBaseURL(config);
120
120
  const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
121
+ const deadline = Date.now() + TCG_POLL_MAX_WAIT_MS;
121
122
  while (!done) {
123
+ // Bail out if the backend never sends a "termination" message, so a stuck
124
+ // job cannot keep this loop (and its callers) alive forever.
125
+ if (Date.now() > deadline) {
126
+ throw new Error(`TCG test-case detail polling timed out after ${TCG_POLL_MAX_WAIT_MS}ms (trace ${traceRequestId})`);
127
+ }
122
128
  // add a bit of jitter to avoid synchronized polling storms
123
- await new Promise((r) => setTimeout(r, 10000 + Math.random() * 5000));
129
+ await new Promise((r) => setTimeout(r, TCG_POLL_INTERVAL_MS + Math.random() * 5000));
124
130
  const poll = await apiClient.post({
125
131
  url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceRequestId)}`,
126
132
  headers: {
@@ -159,7 +165,20 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
159
165
  const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
160
166
  // Promisify interval-style polling using a wrapper
161
167
  await new Promise((resolve, reject) => {
162
- const intervalId = setInterval(async () => {
168
+ const timers = {};
169
+ const stop = () => {
170
+ if (timers.interval)
171
+ clearInterval(timers.interval);
172
+ if (timers.timeout)
173
+ clearTimeout(timers.timeout);
174
+ };
175
+ // Hard wall-clock deadline: if the backend never sends a "termination"
176
+ // message, reject instead of letting the interval fire forever.
177
+ timers.timeout = setTimeout(() => {
178
+ stop();
179
+ reject(new Error(`TCG scenario polling timed out after ${TCG_POLL_MAX_WAIT_MS}ms (trace ${traceId})`));
180
+ }, TCG_POLL_MAX_WAIT_MS);
181
+ timers.interval = setInterval(async () => {
163
182
  try {
164
183
  const poll = await apiClient.post({
165
184
  url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceId)}`,
@@ -169,7 +188,7 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
169
188
  body: {},
170
189
  });
171
190
  if (poll.status !== 200) {
172
- clearInterval(intervalId);
191
+ stop();
173
192
  reject(new Error(`Polling error: ${poll.statusText || poll.status}`));
174
193
  return;
175
194
  }
@@ -221,16 +240,16 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
221
240
  }
222
241
  }
223
242
  if (msg.type === "termination") {
224
- clearInterval(intervalId);
243
+ stop();
225
244
  resolve();
226
245
  }
227
246
  }
228
247
  }
229
248
  catch (err) {
230
- clearInterval(intervalId);
249
+ stop();
231
250
  reject(err);
232
251
  }
233
- }, 10000); // 10 second interval
252
+ }, TCG_POLL_INTERVAL_MS);
234
253
  });
235
254
  // once all detail fetches are triggered, wait for them to complete
236
255
  const detailsList = await Promise.all(detailPromises);
@@ -1,4 +1,6 @@
1
1
  export declare const TC_DETAILS_MAX_BATCH = 10;
2
+ export declare const TCG_POLL_INTERVAL_MS = 10000;
3
+ export declare const TCG_POLL_MAX_WAIT_MS: number;
2
4
  export declare const TCG_TRIGGER_URL: (baseUrl: string) => string;
3
5
  export declare const TCG_POLL_URL: (baseUrl: string) => string;
4
6
  export declare const FETCH_DETAILS_URL: (baseUrl: string) => string;
@@ -1,4 +1,9 @@
1
1
  export const TC_DETAILS_MAX_BATCH = 10;
2
+ // Wall-clock bounds for the TCG generation polling loops. Without a hard
3
+ // deadline, a job that never emits a "termination" message keeps the loop
4
+ // (and every caller awaiting it) alive forever.
5
+ export const TCG_POLL_INTERVAL_MS = 10_000;
6
+ export const TCG_POLL_MAX_WAIT_MS = 10 * 60 * 1000; // 10 minutes
2
7
  export const TCG_TRIGGER_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/suggest-test-cases`;
3
8
  export const TCG_POLL_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/test-cases-polling`;
4
9
  export const FETCH_DETAILS_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/fetch-test-case-details`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.4.1-beta.1",
3
+ "version": "1.4.1",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",