Skip to content

Commit 550a4f1

Browse files
authored
Merge pull request #100 from tech-sushant/proxy-agent
feat: implement HTTPS agent support in ApiClient for proxy and CA certificate
2 parents 7253d65 + 8d50fad commit 550a4f1

File tree

4 files changed

+79
-19
lines changed

4 files changed

+79
-19
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@browserstack/mcp-server",
3-
"version": "1.1.9",
3+
"version": "1.2.0",
44
"description": "BrowserStack's Official MCP Server",
55
"main": "dist/index.js",
66
"repository": {

src/lib/apiClient.ts

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
2+
import httpsProxyAgentPkg from "https-proxy-agent";
3+
const { HttpsProxyAgent } = httpsProxyAgentPkg;
4+
import * as https from "https";
5+
import * as fs from "fs";
6+
import config from "../config.js";
27

38
type RequestOptions = {
49
url: string;
@@ -54,15 +59,52 @@ class ApiResponse<T = any> {
5459
}
5560
}
5661

62+
// Utility to create HTTPS agent if needed (proxy/CA)
63+
function getAxiosAgent(): AxiosRequestConfig["httpsAgent"] | undefined {
64+
const proxyHost = config.browserstackLocalOptions.proxyHost;
65+
const proxyPort = config.browserstackLocalOptions.proxyPort;
66+
const caCertPath = config.browserstackLocalOptions.useCaCertificate;
67+
68+
// If both proxy host and port are defined
69+
if (proxyHost && proxyPort) {
70+
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
71+
if (caCertPath && fs.existsSync(caCertPath)) {
72+
// Proxy + CA cert
73+
const ca = fs.readFileSync(caCertPath);
74+
return new HttpsProxyAgent({
75+
host: proxyHost,
76+
port: Number(proxyPort),
77+
ca,
78+
rejectUnauthorized: false, // Set to true if you want strict SSL
79+
});
80+
} else {
81+
// Proxy only
82+
return new HttpsProxyAgent(proxyUrl);
83+
}
84+
} else if (caCertPath && fs.existsSync(caCertPath)) {
85+
// CA only
86+
return new https.Agent({
87+
ca: fs.readFileSync(caCertPath),
88+
rejectUnauthorized: false, // Set to true for strict SSL
89+
});
90+
}
91+
// Default agent (no proxy, no CA)
92+
return undefined;
93+
}
94+
5795
class ApiClient {
5896
private instance = axios.create();
5997

98+
private get axiosAgent() {
99+
return getAxiosAgent();
100+
}
101+
60102
private async requestWrapper<T>(
61-
fn: () => Promise<AxiosResponse<T>>,
103+
fn: (agent: AxiosRequestConfig["httpsAgent"]) => Promise<AxiosResponse<T>>,
62104
raise_error: boolean = true,
63105
): Promise<ApiResponse<T>> {
64106
try {
65-
const res = await fn();
107+
const res = await fn(this.axiosAgent);
66108
return new ApiResponse<T>(res);
67109
} catch (error: any) {
68110
if (error.response && !raise_error) {
@@ -79,7 +121,8 @@ class ApiClient {
79121
raise_error = true,
80122
}: RequestOptions): Promise<ApiResponse<T>> {
81123
return this.requestWrapper<T>(
82-
() => this.instance.get<T>(url, { headers, params }),
124+
(agent) =>
125+
this.instance.get<T>(url, { headers, params, httpsAgent: agent }),
83126
raise_error,
84127
);
85128
}
@@ -91,7 +134,8 @@ class ApiClient {
91134
raise_error = true,
92135
}: RequestOptions): Promise<ApiResponse<T>> {
93136
return this.requestWrapper<T>(
94-
() => this.instance.post<T>(url, body, { headers }),
137+
(agent) =>
138+
this.instance.post<T>(url, body, { headers, httpsAgent: agent }),
95139
raise_error,
96140
);
97141
}
@@ -103,7 +147,8 @@ class ApiClient {
103147
raise_error = true,
104148
}: RequestOptions): Promise<ApiResponse<T>> {
105149
return this.requestWrapper<T>(
106-
() => this.instance.put<T>(url, body, { headers }),
150+
(agent) =>
151+
this.instance.put<T>(url, body, { headers, httpsAgent: agent }),
107152
raise_error,
108153
);
109154
}
@@ -115,7 +160,8 @@ class ApiClient {
115160
raise_error = true,
116161
}: RequestOptions): Promise<ApiResponse<T>> {
117162
return this.requestWrapper<T>(
118-
() => this.instance.patch<T>(url, body, { headers }),
163+
(agent) =>
164+
this.instance.patch<T>(url, body, { headers, httpsAgent: agent }),
119165
raise_error,
120166
);
121167
}
@@ -127,7 +173,8 @@ class ApiClient {
127173
raise_error = true,
128174
}: RequestOptions): Promise<ApiResponse<T>> {
129175
return this.requestWrapper<T>(
130-
() => this.instance.delete<T>(url, { headers, params }),
176+
(agent) =>
177+
this.instance.delete<T>(url, { headers, params, httpsAgent: agent }),
131178
raise_error,
132179
);
133180
}

src/logger.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { pino } from "pino";
22

3-
let logger: any;
3+
// 1. The actual logger instance, swapped out as needed
4+
let currentLogger: any;
45

56
if (process.env.NODE_ENV === "development") {
6-
logger = pino({
7+
currentLogger = pino({
78
level: "debug",
89
transport: {
910
targets: [
@@ -23,8 +24,8 @@ if (process.env.NODE_ENV === "development") {
2324
},
2425
});
2526
} else {
26-
// NULL logger
27-
logger = pino({
27+
// Null logger (logs go to /dev/null or NUL)
28+
currentLogger = pino({
2829
level: "info",
2930
transport: {
3031
target: "pino/file",
@@ -35,12 +36,24 @@ if (process.env.NODE_ENV === "development") {
3536
});
3637
}
3738

38-
/**
39-
* Set a custom logger instance
40-
* @param customLogger - The logger instance to use
41-
*/
39+
// 2. Proxy logger: always delegates to the currentLogger
40+
const logger: any = new Proxy(
41+
{},
42+
{
43+
get(_target, prop) {
44+
// Forward function calls to currentLogger
45+
if (typeof currentLogger[prop] === "function") {
46+
return (...args: any[]) => currentLogger[prop](...args);
47+
}
48+
// Forward property gets
49+
return currentLogger[prop];
50+
},
51+
},
52+
);
53+
54+
// 3. Setter to update the logger instance everywhere
4255
export function setLogger(customLogger: any): void {
43-
logger = customLogger;
56+
currentLogger = customLogger;
4457
}
4558

4659
export default logger;

0 commit comments

Comments
 (0)