From fe7da99dfdbae81c01cdcc3909a82abc3e39a395 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 07:48:08 +0000 Subject: [PATCH] Add comprehensive proxy support documentation and tests Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com> --- README.fr.md | 1 + README.md | 1 + README.zh.md | 1 + docs/configuration/proxy-support.mdx | 368 +++++++++++++++++++ examples/mcp_settings_with_env_vars.json | 25 ++ tests/services/mcpService-proxy.test.ts | 447 +++++++++++++++++++++++ 6 files changed, 843 insertions(+) create mode 100644 docs/configuration/proxy-support.mdx create mode 100644 tests/services/mcpService-proxy.test.ts diff --git a/README.fr.md b/README.fr.md index dc27228..bb1476c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -19,6 +19,7 @@ MCPHub facilite la gestion et la mise à l'échelle de plusieurs serveurs MCP (M - **Configuration à chaud** : Ajoutez, supprimez ou mettez à jour les serveurs MCP à la volée, sans temps d'arrêt. - **Contrôle d'accès basé sur les groupes** : Organisez les serveurs en groupes personnalisables pour une gestion simplifiée des autorisations. - **Authentification sécurisée** : Gestion des utilisateurs intégrée avec contrôle d'accès basé sur les rôles, optimisée par JWT et bcrypt. +- **Support de proxy** : Configurez des proxys HTTP/HTTPS pour les serveurs MCP qui doivent accéder à des ressources externes. Voir le [Guide de support proxy](docs/configuration/proxy-support.mdx). - **Prêt pour Docker** : Déployez instantanément avec notre configuration conteneurisée. ## 🔧 Démarrage rapide diff --git a/README.md b/README.md index b1017df..27638a1 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ MCPHub makes it easy to manage and scale multiple MCP (Model Context Protocol) s - **Secure Authentication**: Built-in user management with role-based access powered by JWT and bcrypt. - **OAuth 2.0 Support**: Full OAuth support for upstream MCP servers with proxy authorization capabilities. - **Environment Variable Expansion**: Use environment variables anywhere in your configuration for secure credential management. See [Environment Variables Guide](docs/environment-variables.md). +- **Proxy Support**: Configure HTTP/HTTPS proxies for MCP servers that need to access external resources. See [Proxy Support Guide](docs/configuration/proxy-support.mdx). - **Docker-Ready**: Deploy instantly with our containerized setup. ## 🔧 Quick Start diff --git a/README.zh.md b/README.zh.md index 7327401..b87df70 100644 --- a/README.zh.md +++ b/README.zh.md @@ -19,6 +19,7 @@ MCPHub 通过将多个 MCP(Model Context Protocol)服务器组织为灵活 - **热插拔式配置**:在运行时动态添加、移除或更新服务器配置,无需停机。 - **基于分组的访问控制**:自定义分组并管理服务器访问权限。 - **安全认证机制**:内置用户管理,基于 JWT 和 bcrypt,实现角色权限控制。 +- **代理支持**:为需要通过代理访问外部资源的 MCP 服务器配置 HTTP/HTTPS 代理。参见[代理支持指南](docs/configuration/proxy-support.mdx)。 - **Docker 就绪**:提供容器化镜像,快速部署。 ## 🔧 快速开始 diff --git a/docs/configuration/proxy-support.mdx b/docs/configuration/proxy-support.mdx new file mode 100644 index 0000000..396a84a --- /dev/null +++ b/docs/configuration/proxy-support.mdx @@ -0,0 +1,368 @@ +# Proxy Support for MCP Servers + +## Overview + +MCPHub supports configuring proxy servers for MCP servers that need to access external resources through a proxy. This is particularly useful when: + +- Your MCP servers need to install packages from external repositories +- Your network environment requires proxy for internet access +- You need to route MCP server traffic through a specific proxy + +## How It Works + +For **stdio-based MCP servers** (servers that spawn child processes), MCPHub automatically passes environment variables to the spawned process. This includes standard proxy environment variables: + +- `HTTP_PROXY` / `http_proxy` - Proxy server for HTTP connections +- `HTTPS_PROXY` / `https_proxy` - Proxy server for HTTPS connections +- `NO_PROXY` / `no_proxy` - Comma-separated list of hosts that should bypass the proxy + +## Configuration Methods + +### Method 1: System-wide Environment Variables + +Set proxy environment variables before starting MCPHub. These will be inherited by all MCP servers: + +```bash +# Linux/macOS +export HTTP_PROXY="http://proxy.example.com:8080" +export HTTPS_PROXY="http://proxy.example.com:8080" +export NO_PROXY="localhost,127.0.0.1,.local" + +# Start MCPHub +npm start +``` + +```powershell +# Windows PowerShell +$env:HTTP_PROXY="http://proxy.example.com:8080" +$env:HTTPS_PROXY="http://proxy.example.com:8080" +$env:NO_PROXY="localhost,127.0.0.1,.local" + +# Start MCPHub +npm start +``` + +### Method 2: Per-Server Configuration + +Configure proxy settings for specific MCP servers in `mcp_settings.json`: + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-example"], + "env": { + "HTTP_PROXY": "http://proxy.example.com:8080", + "HTTPS_PROXY": "http://proxy.example.com:8080", + "NO_PROXY": "localhost,127.0.0.1" + } + } + } +} +``` + +### Method 3: Using Environment Variable References + +Combine per-server configuration with environment variable references for flexibility: + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "uvx", + "args": ["mcp-server-fetch"], + "env": { + "HTTP_PROXY": "${COMPANY_HTTP_PROXY}", + "HTTPS_PROXY": "${COMPANY_HTTPS_PROXY}", + "NO_PROXY": "${COMPANY_NO_PROXY}" + } + } + } +} +``` + +Then set the environment variables: + +```bash +export COMPANY_HTTP_PROXY="http://proxy.example.com:8080" +export COMPANY_HTTPS_PROXY="http://proxy.example.com:8080" +export COMPANY_NO_PROXY="localhost,127.0.0.1,.local" +``` + +## Docker Usage + +### Method 1: Docker Environment Variables + +Pass proxy settings when running MCPHub container: + +```bash +docker run \ + -e HTTP_PROXY="http://proxy.example.com:8080" \ + -e HTTPS_PROXY="http://proxy.example.com:8080" \ + -e NO_PROXY="localhost,127.0.0.1" \ + -v $(pwd)/mcp_settings.json:/app/mcp_settings.json \ + samanhappy/mcphub:latest +``` + +### Method 2: Docker Compose + +Configure proxy in `docker-compose.yml`: + +```yaml +version: '3.8' +services: + mcphub: + image: samanhappy/mcphub:latest + environment: + - HTTP_PROXY=http://proxy.example.com:8080 + - HTTPS_PROXY=http://proxy.example.com:8080 + - NO_PROXY=localhost,127.0.0.1,.local + volumes: + - ./mcp_settings.json:/app/mcp_settings.json +``` + +### Method 3: Docker Environment File + +Create a `.env` file: + +```bash +# .env +HTTP_PROXY=http://proxy.example.com:8080 +HTTPS_PROXY=http://proxy.example.com:8080 +NO_PROXY=localhost,127.0.0.1,.local +``` + +Use it with Docker: + +```bash +docker run --env-file .env -v $(pwd)/mcp_settings.json:/app/mcp_settings.json samanhappy/mcphub:latest +``` + +Or with docker-compose.yml: + +```yaml +version: '3.8' +services: + mcphub: + image: samanhappy/mcphub:latest + env_file: + - .env + volumes: + - ./mcp_settings.json:/app/mcp_settings.json +``` + +## Examples + +### Example 1: NPM Packages with Proxy + +Configure an MCP server that uses NPM packages through a proxy: + +```json +{ + "mcpServers": { + "playwright-server": { + "type": "stdio", + "command": "npx", + "args": ["@playwright/mcp@latest", "--headless"], + "env": { + "HTTP_PROXY": "http://proxy.company.com:8080", + "HTTPS_PROXY": "http://proxy.company.com:8080", + "NO_PROXY": "localhost,127.0.0.1,.company.com" + } + } + } +} +``` + +### Example 2: Python UVX with Proxy + +Configure a Python-based MCP server that needs proxy for package installation: + +```json +{ + "mcpServers": { + "fetch-server": { + "type": "stdio", + "command": "uvx", + "args": ["mcp-server-fetch"], + "env": { + "HTTP_PROXY": "http://proxy.company.com:8080", + "HTTPS_PROXY": "http://proxy.company.com:8080", + "NO_PROXY": "localhost,127.0.0.1,*.internal" + } + } + } +} +``` + +### Example 3: Mixed Configuration + +Some servers use proxy, others don't: + +```json +{ + "mcpServers": { + "external-api": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@external/mcp-server"], + "env": { + "HTTP_PROXY": "${EXTERNAL_PROXY}", + "HTTPS_PROXY": "${EXTERNAL_PROXY}" + } + }, + "internal-service": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@internal/mcp-server"], + "env": { + "NO_PROXY": "*" + } + } + } +} +``` + +## Proxy Authentication + +If your proxy requires authentication, include credentials in the proxy URL: + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "npx", + "args": ["-y", "my-mcp-server"], + "env": { + "HTTP_PROXY": "http://username:password@proxy.example.com:8080", + "HTTPS_PROXY": "http://username:password@proxy.example.com:8080" + } + } + } +} +``` + +**Security Note**: For better security, use environment variable references to avoid storing credentials in configuration files: + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "npx", + "args": ["-y", "my-mcp-server"], + "env": { + "HTTP_PROXY": "${PROXY_URL_WITH_CREDENTIALS}" + } + } + } +} +``` + +Then set the environment variable: + +```bash +export PROXY_URL_WITH_CREDENTIALS="http://username:password@proxy.example.com:8080" +``` + +## Troubleshooting + +### Proxy Not Working + +1. **Verify proxy configuration**: + ```bash + echo $HTTP_PROXY + echo $HTTPS_PROXY + ``` + +2. **Check proxy connectivity**: + ```bash + curl -x http://proxy.example.com:8080 https://www.google.com + ``` + +3. **Test with a simple command**: + ```bash + HTTP_PROXY=http://proxy.example.com:8080 curl https://api.github.com + ``` + +4. **Check MCP server logs**: + - Look for connection errors in MCPHub console output + - Check if the MCP server reports proxy-related errors + +### Certificate Issues + +If using HTTPS proxy with self-signed certificates: + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "npx", + "args": ["-y", "my-mcp-server"], + "env": { + "HTTPS_PROXY": "http://proxy.example.com:8080", + "NODE_TLS_REJECT_UNAUTHORIZED": "0" + } + } + } +} +``` + +**Warning**: Disabling certificate validation (`NODE_TLS_REJECT_UNAUTHORIZED=0`) should only be used in development or trusted networks. + +### Proxy for Package Installation Only + +If you only need proxy for package installation (not runtime): + +For NPM: +```bash +npm config set proxy http://proxy.example.com:8080 +npm config set https-proxy http://proxy.example.com:8080 +``` + +For Python/UV: +```bash +export UV_HTTP_PROXY=http://proxy.example.com:8080 +``` + +## Limitations + +### SSE and HTTP-based MCP Servers + +The proxy configuration described here applies to **stdio-based MCP servers** (servers that spawn child processes). + +For **SSE** or **HTTP-based** MCP servers, MCPHub itself acts as the HTTP client. In these cases: + +1. Set proxy environment variables for the MCPHub process (not in `mcp_settings.json`) +2. The Node.js HTTP client will automatically use these proxy settings + +Example for SSE servers: + +```bash +# Set proxy for MCPHub process +export HTTP_PROXY="http://proxy.example.com:8080" +export HTTPS_PROXY="http://proxy.example.com:8080" + +# Start MCPHub - it will use these settings for SSE/HTTP connections +npm start +``` + +## Best Practices + +1. **Use environment variables** for proxy credentials to avoid storing them in configuration files +2. **Configure NO_PROXY** to exclude internal services and localhost +3. **Test proxy configuration** before deploying to production +4. **Document proxy requirements** for your deployment environment +5. **Use separate proxy configurations** for different MCP servers when needed +6. **Monitor proxy logs** for connection issues and performance + +## Related Documentation + +- [Environment Variables](../environment-variables.md) - Learn about environment variable expansion +- [Docker Setup](./docker-setup.mdx) - Docker-specific configuration +- [MCP Settings](./mcp-settings.mdx) - Complete configuration reference diff --git a/examples/mcp_settings_with_env_vars.json b/examples/mcp_settings_with_env_vars.json index 6a5701c..91ca1d8 100644 --- a/examples/mcp_settings_with_env_vars.json +++ b/examples/mcp_settings_with_env_vars.json @@ -31,6 +31,31 @@ "DATABASE_URL": "${DATABASE_URL}" } }, + "example-stdio-server-with-proxy": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-example" + ], + "env": { + "HTTP_PROXY": "${HTTP_PROXY}", + "HTTPS_PROXY": "${HTTPS_PROXY}", + "NO_PROXY": "${NO_PROXY}" + } + }, + "example-python-server-with-proxy": { + "type": "stdio", + "command": "uvx", + "args": [ + "mcp-server-fetch" + ], + "env": { + "HTTP_PROXY": "http://proxy.example.com:8080", + "HTTPS_PROXY": "http://proxy.example.com:8080", + "NO_PROXY": "localhost,127.0.0.1,.local" + } + }, "example-openapi-server": { "type": "openapi", "openapi": { diff --git a/tests/services/mcpService-proxy.test.ts b/tests/services/mcpService-proxy.test.ts new file mode 100644 index 0000000..c091d02 --- /dev/null +++ b/tests/services/mcpService-proxy.test.ts @@ -0,0 +1,447 @@ +import { replaceEnvVars } from '../../src/config/index.js'; +import { ServerConfig } from '../../src/types/index.js'; + +describe('MCP Service - Proxy Support', () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...originalEnv }; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + describe('Proxy environment variables in server configuration', () => { + it('should expand HTTP_PROXY in env configuration', () => { + process.env.HTTP_PROXY = 'http://proxy.example.com:8080'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', '@modelcontextprotocol/server-example'], + env: { + HTTP_PROXY: '${HTTP_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + }); + + it('should expand HTTPS_PROXY in env configuration', () => { + process.env.HTTPS_PROXY = 'http://proxy.example.com:8080'; + + const config: ServerConfig = { + type: 'stdio', + command: 'uvx', + args: ['mcp-server-fetch'], + env: { + HTTPS_PROXY: '${HTTPS_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.example.com:8080'); + }); + + it('should expand NO_PROXY in env configuration', () => { + process.env.NO_PROXY = 'localhost,127.0.0.1,.local'; + + const config: ServerConfig = { + type: 'stdio', + command: 'node', + args: ['server.js'], + env: { + NO_PROXY: '${NO_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1,.local'); + }); + + it('should expand all proxy environment variables together', () => { + process.env.HTTP_PROXY = 'http://proxy.example.com:8080'; + process.env.HTTPS_PROXY = 'http://proxy.example.com:8080'; + process.env.NO_PROXY = 'localhost,127.0.0.1'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['my-server'], + env: { + HTTP_PROXY: '${HTTP_PROXY}', + HTTPS_PROXY: '${HTTPS_PROXY}', + NO_PROXY: '${NO_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.example.com:8080'); + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1'); + }); + }); + + describe('Static proxy configuration in server config', () => { + it('should preserve static proxy values from server config', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://custom-proxy.example.com:3128', + HTTPS_PROXY: 'http://custom-proxy.example.com:3128', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://custom-proxy.example.com:3128'); + expect(result.env?.HTTPS_PROXY).toBe('http://custom-proxy.example.com:3128'); + }); + + it('should expand environment variable references in proxy configuration', () => { + process.env.PROXY_HOST = 'proxy.example.com'; + process.env.PROXY_PORT = '8080'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://${PROXY_HOST}:${PROXY_PORT}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + }); + + it('should expand proxy URL from single environment variable', () => { + process.env.COMPANY_PROXY = 'http://proxy.company.com:3128'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: '${COMPANY_PROXY}', + HTTPS_PROXY: '${COMPANY_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.company.com:3128'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.company.com:3128'); + }); + }); + + describe('Proxy authentication', () => { + it('should preserve proxy authentication in URL', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://user:pass@proxy.example.com:8080', + HTTPS_PROXY: 'http://user:pass@proxy.example.com:8080', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://user:pass@proxy.example.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://user:pass@proxy.example.com:8080'); + }); + + it('should expand proxy credentials from environment variables', () => { + process.env.PROXY_USERNAME = 'user'; + process.env.PROXY_PASSWORD = 'secret'; + process.env.PROXY_HOST = 'proxy.example.com'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://${PROXY_USERNAME}:${PROXY_PASSWORD}@${PROXY_HOST}:8080', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://user:secret@proxy.example.com:8080'); + }); + + it('should expand complete proxy URL with credentials from environment variable', () => { + process.env.AUTHENTICATED_PROXY = 'http://admin:password123@secure-proxy.local:3128'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: '${AUTHENTICATED_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://admin:password123@secure-proxy.local:3128'); + }); + }); + + describe('NO_PROXY configuration', () => { + it('should preserve NO_PROXY for excluding hosts', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://proxy.example.com:8080', + NO_PROXY: 'localhost,127.0.0.1,*.internal,.local', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1,*.internal,.local'); + }); + + it('should preserve NO_PROXY=* to disable proxy for all hosts', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://proxy.example.com:8080', + NO_PROXY: '*', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.NO_PROXY).toBe('*'); + }); + + it('should expand NO_PROXY from environment variable', () => { + process.env.COMPANY_NO_PROXY = 'localhost,127.0.0.1,*.company.com,.internal'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://proxy.company.com:8080', + NO_PROXY: '${COMPANY_NO_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1,*.company.com,.internal'); + }); + }); + + describe('Mixed proxy configurations', () => { + it('should support different proxies for different servers', () => { + const config1: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'server1'], + env: { + HTTP_PROXY: 'http://proxy1.example.com:8080', + }, + }; + + const config2: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'server2'], + env: { + HTTP_PROXY: 'http://proxy2.example.com:3128', + }, + }; + + const result1 = replaceEnvVars(config1) as ServerConfig; + const result2 = replaceEnvVars(config2) as ServerConfig; + + expect(result1.env?.HTTP_PROXY).toBe('http://proxy1.example.com:8080'); + expect(result2.env?.HTTP_PROXY).toBe('http://proxy2.example.com:3128'); + }); + + it('should support proxy for some servers and not others', () => { + const configWithProxy: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'external-server'], + env: { + HTTP_PROXY: 'http://proxy.example.com:8080', + }, + }; + + const configWithoutProxy: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'internal-server'], + env: { + NO_PROXY: '*', + }, + }; + + const result1 = replaceEnvVars(configWithProxy) as ServerConfig; + const result2 = replaceEnvVars(configWithoutProxy) as ServerConfig; + + expect(result1.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + expect(result2.env?.NO_PROXY).toBe('*'); + }); + + it('should support mixing environment variable references and static values', () => { + process.env.PRIMARY_PROXY = 'http://proxy1.company.com:8080'; + + const config1: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'server1'], + env: { + HTTP_PROXY: '${PRIMARY_PROXY}', + }, + }; + + const config2: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'server2'], + env: { + HTTP_PROXY: 'http://proxy2.company.com:3128', + }, + }; + + const result1 = replaceEnvVars(config1) as ServerConfig; + const result2 = replaceEnvVars(config2) as ServerConfig; + + expect(result1.env?.HTTP_PROXY).toBe('http://proxy1.company.com:8080'); + expect(result2.env?.HTTP_PROXY).toBe('http://proxy2.company.com:3128'); + }); + }); + + describe('Proxy with other environment variables', () => { + it('should support proxy alongside API keys and other env vars', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'my-server'], + env: { + HTTP_PROXY: 'http://proxy.example.com:8080', + HTTPS_PROXY: 'http://proxy.example.com:8080', + API_KEY: 'secret-key-123', + DEBUG: 'true', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.example.com:8080'); + expect(result.env?.API_KEY).toBe('secret-key-123'); + expect(result.env?.DEBUG).toBe('true'); + }); + + it('should expand mix of proxy and other environment variables', () => { + process.env.PROXY_URL = 'http://proxy.company.com:8080'; + process.env.MY_API_KEY = 'api-key-xyz'; + process.env.DATABASE_URL = 'postgresql://localhost/mydb'; + + const config: ServerConfig = { + type: 'stdio', + command: 'uvx', + args: ['mcp-server-example'], + env: { + HTTP_PROXY: '${PROXY_URL}', + HTTPS_PROXY: '${PROXY_URL}', + API_KEY: '${MY_API_KEY}', + DATABASE_URL: '${DATABASE_URL}', + DEBUG: 'true', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.company.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.company.com:8080'); + expect(result.env?.API_KEY).toBe('api-key-xyz'); + expect(result.env?.DATABASE_URL).toBe('postgresql://localhost/mydb'); + expect(result.env?.DEBUG).toBe('true'); + }); + }); + + describe('Real-world proxy scenarios', () => { + it('should handle corporate proxy configuration', () => { + process.env.CORPORATE_PROXY = 'http://proxy.corp.com:8080'; + process.env.CORPORATE_NO_PROXY = 'localhost,127.0.0.1,*.corp.com,.internal'; + + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', '@modelcontextprotocol/server-example'], + env: { + HTTP_PROXY: '${CORPORATE_PROXY}', + HTTPS_PROXY: '${CORPORATE_PROXY}', + NO_PROXY: '${CORPORATE_NO_PROXY}', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.corp.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.corp.com:8080'); + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1,*.corp.com,.internal'); + }); + + it('should handle Python package installation with proxy', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'uvx', + args: ['mcp-server-fetch'], + env: { + HTTP_PROXY: 'http://proxy.example.com:8080', + HTTPS_PROXY: 'http://proxy.example.com:8080', + NO_PROXY: 'localhost,127.0.0.1', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.example.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.example.com:8080'); + }); + + it('should handle NPM package installation with proxy', () => { + const config: ServerConfig = { + type: 'stdio', + command: 'npx', + args: ['@playwright/mcp@latest', '--headless'], + env: { + HTTP_PROXY: 'http://proxy.company.com:8080', + HTTPS_PROXY: 'http://proxy.company.com:8080', + NO_PROXY: 'localhost,127.0.0.1,.company.com', + }, + }; + + const result = replaceEnvVars(config) as ServerConfig; + + expect(result.env?.HTTP_PROXY).toBe('http://proxy.company.com:8080'); + expect(result.env?.HTTPS_PROXY).toBe('http://proxy.company.com:8080'); + expect(result.env?.NO_PROXY).toBe('localhost,127.0.0.1,.company.com'); + }); + }); +});