Skip to content

Commit e7ea62d

Browse files
manoharnvCopilot
andauthored
Add proxy support for corporate environments (#209)
* Add proxy support for corporate environments - Created proxy-config utility to configure undici ProxyAgent - Added support for https_proxy, HTTPS_PROXY, http_proxy, HTTP_PROXY, no_proxy, and NO_PROXY environment variables - Proxy is configured early in startup process before any HTTP requests - Added comprehensive test coverage for proxy configuration - Updated README.md with proxy environment variable documentation - Updated CHANGELOG.md to document new feature Co-authored-by: manoharnv <[email protected]> * Address code review feedback - Remove unnecessary log message when no proxy is configured - Clarify no_proxy limitation in code comments and README - Update .env.template with proxy configuration examples Co-authored-by: manoharnv <[email protected]> * Update README.md Fixing as per review comment. placing the proxy configuration at the right place. * Remove unused shouldBypassProxy function and all references Co-authored-by: manoharnv <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: manoharnv <[email protected]>
1 parent 40bf6a2 commit e7ea62d

File tree

6 files changed

+194
-0
lines changed

6 files changed

+194
-0
lines changed

.env.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
OAUTH_CLIENT_ID=dt0s02.SAMPLE
22
OAUTH_CLIENT_SECRET=dt0s02.SAMPLE.ABCD1234
33
DT_ENVIRONMENT=https://abcd1234.apps.dynatrace.com
4+
5+
# Proxy configuration (optional - for corporate environments)
6+
# HTTPS_PROXY=http://proxy.example.com:8080
7+
# NO_PROXY=localhost,127.0.0.1,.local

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717

1818
- Fixed an issue where disabling telemetry with `DT_MCP_DISABLE_TELEMETRY=true` would show a stack trace instead of a concise message.
1919

20+
### Proxy Support
21+
22+
- Added support for system proxy configuration via environment variables (`https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`, `no_proxy`, `NO_PROXY`)
23+
- The MCP server now honors corporate proxy settings for all HTTP requests to Dynatrace environments
24+
25+
### Other Changes
26+
27+
- Removed unused `shouldBypassProxy` function from proxy configuration utilities
28+
2029
## 0.9.2
2130

2231
- Improved error handling when initializing the connection for the first time

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,24 @@ In addition, depending on the features you use, the following variables can be c
298298

299299
- `SLACK_CONNECTION_ID` (string) - connection ID of a [Slack Connection](https://docs.dynatrace.com/docs/analyze-explore-automate/workflows/actions/slack)
300300

301+
### Proxy Configuration
302+
303+
The MCP server honors system proxy settings for corporate environments:
304+
305+
- `https_proxy` or `HTTPS_PROXY` (optional, string, e.g., `http://proxy.example.com:8080`) - Proxy server URL for HTTPS requests
306+
- `http_proxy` or `HTTP_PROXY` (optional, string, e.g., `http://proxy.example.com:8080`) - Proxy server URL for HTTP requests
307+
- `no_proxy` or `NO_PROXY` (optional, string, e.g., `localhost,127.0.0.1,.local`) - Comma-separated list of hostnames or domains that should bypass the proxy
308+
309+
**Note:** The `no_proxy` environment variable is currently logged for informational purposes but not fully enforced by the underlying HTTP client. If you need to bypass the proxy for specific hosts, consider configuring your proxy server to handle these exclusions.
310+
311+
Example configuration with proxy:
312+
313+
```bash
314+
export HTTPS_PROXY=http://proxy.company.com:8080
315+
export NO_PROXY=localhost,127.0.0.1,.company.local
316+
export DT_ENVIRONMENT=https://abc12345.apps.dynatrace.com
317+
```
318+
301319
### Scopes for Authentication
302320

303321
Depending on the features you are using, the following scopes are needed:

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { createTelemetry, Telemetry } from './utils/telemetry-openkit';
4747
import { getEntityTypeFromId } from './utils/dynatrace-entity-types';
4848
import { resetGrailBudgetTracker, getGrailBudgetTracker } from './utils/grail-budget-tracker';
4949
import { handleClientRequestError } from './utils/dynatrace-connection-utils';
50+
import { configureProxyFromEnvironment } from './utils/proxy-config';
5051

5152
// Load environment variables from .env file if available, and suppress warnings/logging to stdio
5253
// as it breaks MCP communication when using stdio transport
@@ -110,6 +111,9 @@ const allRequiredScopes = scopesBase.concat([
110111
const main = async () => {
111112
console.error(`Initializing Dynatrace MCP Server v${getPackageJsonVersion()}...`);
112113

114+
// Configure proxy from environment variables early in the startup process
115+
configureProxyFromEnvironment();
116+
113117
// read Environment variables
114118
let dynatraceEnv: DynatraceEnv;
115119
try {

src/utils/proxy-config.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { configureProxyFromEnvironment } from './proxy-config';
2+
3+
// Mock undici
4+
jest.mock('undici', () => ({
5+
ProxyAgent: jest.fn(),
6+
setGlobalDispatcher: jest.fn(),
7+
getGlobalDispatcher: jest.fn(),
8+
}));
9+
10+
import { ProxyAgent, setGlobalDispatcher } from 'undici';
11+
12+
describe('proxy-config', () => {
13+
let originalEnv: NodeJS.ProcessEnv;
14+
15+
beforeEach(() => {
16+
// Save original environment
17+
originalEnv = { ...process.env };
18+
19+
// Clear all proxy-related env vars
20+
delete process.env.https_proxy;
21+
delete process.env.HTTPS_PROXY;
22+
delete process.env.http_proxy;
23+
delete process.env.HTTP_PROXY;
24+
delete process.env.no_proxy;
25+
delete process.env.NO_PROXY;
26+
27+
// Clear mocks
28+
jest.clearAllMocks();
29+
});
30+
31+
afterEach(() => {
32+
// Restore original environment
33+
process.env = originalEnv;
34+
});
35+
36+
describe('configureProxyFromEnvironment', () => {
37+
it('should configure proxy when HTTPS_PROXY is set', () => {
38+
process.env.HTTPS_PROXY = 'http://proxy.example.com:8080';
39+
40+
configureProxyFromEnvironment();
41+
42+
expect(ProxyAgent).toHaveBeenCalledWith({
43+
uri: 'http://proxy.example.com:8080',
44+
});
45+
expect(setGlobalDispatcher).toHaveBeenCalled();
46+
});
47+
48+
it('should configure proxy when https_proxy is set (lowercase)', () => {
49+
process.env.https_proxy = 'http://proxy.example.com:8080';
50+
51+
configureProxyFromEnvironment();
52+
53+
expect(ProxyAgent).toHaveBeenCalledWith({
54+
uri: 'http://proxy.example.com:8080',
55+
});
56+
expect(setGlobalDispatcher).toHaveBeenCalled();
57+
});
58+
59+
it('should prefer HTTPS_PROXY over HTTP_PROXY', () => {
60+
process.env.HTTPS_PROXY = 'http://https-proxy.example.com:8443';
61+
process.env.HTTP_PROXY = 'http://http-proxy.example.com:8080';
62+
63+
configureProxyFromEnvironment();
64+
65+
expect(ProxyAgent).toHaveBeenCalledWith({
66+
uri: 'http://https-proxy.example.com:8443',
67+
});
68+
});
69+
70+
it('should fall back to HTTP_PROXY if HTTPS_PROXY is not set', () => {
71+
process.env.HTTP_PROXY = 'http://proxy.example.com:8080';
72+
73+
configureProxyFromEnvironment();
74+
75+
expect(ProxyAgent).toHaveBeenCalledWith({
76+
uri: 'http://proxy.example.com:8080',
77+
});
78+
});
79+
80+
it('should not configure proxy when no proxy env vars are set', () => {
81+
configureProxyFromEnvironment();
82+
83+
expect(ProxyAgent).not.toHaveBeenCalled();
84+
expect(setGlobalDispatcher).not.toHaveBeenCalled();
85+
});
86+
87+
it('should handle errors gracefully', () => {
88+
process.env.HTTPS_PROXY = 'http://proxy.example.com:8080';
89+
const mockProxyAgent = ProxyAgent as unknown as jest.Mock;
90+
mockProxyAgent.mockImplementation(() => {
91+
throw new Error('ProxyAgent error');
92+
});
93+
94+
// Should not throw
95+
expect(() => configureProxyFromEnvironment()).not.toThrow();
96+
97+
// Should still attempt to create ProxyAgent
98+
expect(ProxyAgent).toHaveBeenCalled();
99+
// Should not set dispatcher if ProxyAgent creation fails
100+
expect(setGlobalDispatcher).not.toHaveBeenCalled();
101+
});
102+
});
103+
});

src/utils/proxy-config.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { ProxyAgent, setGlobalDispatcher, getGlobalDispatcher } from 'undici';
2+
3+
/**
4+
* Parse and configure system proxy settings from environment variables.
5+
* Supports https_proxy, HTTPS_PROXY, http_proxy, HTTP_PROXY, no_proxy, and NO_PROXY.
6+
*
7+
* This function should be called early in the application lifecycle to ensure
8+
* all HTTP requests honor the system proxy settings.
9+
*/
10+
export function configureProxyFromEnvironment(): void {
11+
// Check for proxy environment variables (case-insensitive)
12+
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY;
13+
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY;
14+
const noProxy = process.env.no_proxy || process.env.NO_PROXY;
15+
16+
// Determine which proxy to use (prefer HTTPS proxy for HTTPS requests)
17+
const proxyUrl = httpsProxy || httpProxy;
18+
19+
if (!proxyUrl) {
20+
// No proxy configured, nothing to do
21+
return;
22+
}
23+
24+
try {
25+
console.error(`Configuring proxy from environment: ${proxyUrl}`);
26+
27+
// Parse no_proxy list if provided
28+
let noProxyHosts: string[] = [];
29+
if (noProxy) {
30+
// Split by comma and trim whitespace
31+
noProxyHosts = noProxy
32+
.split(',')
33+
.map((host) => host.trim())
34+
.filter((host) => host.length > 0);
35+
console.error(`No proxy hosts configured: ${noProxyHosts.join(', ')}`);
36+
}
37+
38+
// Create ProxyAgent with the configured proxy URL
39+
// Note: undici's ProxyAgent doesn't have built-in no_proxy support.
40+
// The no_proxy environment variable is logged for informational purposes,
41+
// but the ProxyAgent will route all requests through the proxy.
42+
// If no_proxy support is critical for your use case, you may need to
43+
// configure your proxy server to handle no_proxy exclusions.
44+
const proxyAgent = new ProxyAgent({
45+
uri: proxyUrl,
46+
});
47+
48+
// Set the global dispatcher for undici (affects global fetch)
49+
setGlobalDispatcher(proxyAgent);
50+
51+
console.error(`✅ Proxy configured successfully: ${proxyUrl}`);
52+
} catch (error) {
53+
console.error(`⚠️ Failed to configure proxy: ${error instanceof Error ? error.message : String(error)}`);
54+
console.error('Continuing without proxy configuration.');
55+
}
56+
}

0 commit comments

Comments
 (0)