Add proxy support for MCP server connections

This commit is contained in:
uiyzzi 2026-04-01 18:52:40 +08:00
parent a9c76eca21
commit 4ab833ca7b
4 changed files with 116 additions and 16 deletions

View file

@ -301,14 +301,16 @@ and injected into the context for a configured number of turns (`ttl`).
| `env_file` | string | no | Path to environment file for stdio process |
| `url` | string | sse/http | Endpoint URL for `sse`/`http` transport |
| `headers` | object | no | HTTP headers for `sse`/`http` transport |
| `proxy` | string | no | HTTP proxy URL (http/https/socks5/socks5h). For `sse`/`http`: used for HTTP requests. For `stdio`: sets `HTTP_PROXY`/`HTTPS_PROXY` env vars for the subprocess. When empty, uses environment proxy settings. |
### Transport Behavior
- If `type` is omitted, transport is auto-detected:
- `url` is set → `sse`
- `command` is set → `stdio`
- `http` and `sse` both use `url` + optional `headers`.
- `env` and `env_file` are only applied to `stdio` servers.
- `http` and `sse` both use `url` + optional `headers` and `proxy`.
- `env`, `env_file`, and `proxy` are only applied to `stdio` servers (via environment variables).
- All transport types support the `proxy` option. Supports http, https, socks5, and socks5h schemes.
### Configuration Examples
@ -357,6 +359,31 @@ and injected into the context for a configured number of turns (`ttl`).
}
```
#### 2.1) Remote MCP server with proxy
```json
{
"tools": {
"mcp": {
"enabled": true,
"servers": {
"remote-mcp-behind-proxy": {
"enabled": true,
"type": "sse",
"url": "https://internal-mcp.example.com/mcp",
"proxy": "socks5://127.0.0.1:1080",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
}
}
```
Supported proxy schemes: `http`, `https`, `socks5`, `socks5h`. When `proxy` is not set, the system environment proxy settings (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) are used.
#### 3) Massive MCP setup with Tool Discovery enabled
*In this example, the LLM will only see the `tool_search_tool_bm25`. It will search and unlock Github or Postgres tools

View file

@ -277,14 +277,16 @@ LLM 不会加载所有工具,而是获得一个轻量级搜索工具(使用
| `env_file` | string | 否 | stdio 进程的环境文件路径 |
| `url` | string | sse/http | `sse`/`http` 传输的端点 URL |
| `headers` | object | 否 | `sse`/`http` 传输的 HTTP 头 |
| `proxy` | string | 否 | HTTP 代理 URL支持 http/https/socks5/socks5h。对于 `sse`/`http`:用于 HTTP 请求。对于 `stdio`:设置子进程的 `HTTP_PROXY`/`HTTPS_PROXY` 环境变量。为空时使用系统环境代理设置。 |
### 传输行为
- 如果省略 `type`,传输方式将自动检测:
- 设置了 `url``sse`
- 设置了 `command``stdio`
- `http``sse` 都使用 `url` + 可选的 `headers`
- `env``env_file` 仅应用于 `stdio` 服务器。
- `http``sse` 都使用 `url` + 可选的 `headers``proxy`
- `env``env_file``proxy` 仅应用于 `stdio` 服务器(通过环境变量)。
- 所有传输类型都支持 `proxy` 选项,支持 http、https、socks5 和 socks5h 协议。
### 配置示例
@ -333,6 +335,31 @@ LLM 不会加载所有工具,而是获得一个轻量级搜索工具(使用
}
```
#### 2.1) 通过代理连接远程 MCP 服务器
```json
{
"tools": {
"mcp": {
"enabled": true,
"servers": {
"remote-mcp-behind-proxy": {
"enabled": true,
"type": "sse",
"url": "https://internal-mcp.example.com/mcp",
"proxy": "socks5://127.0.0.1:1080",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
}
}
```
支持的代理协议:`http``https``socks5``socks5h`。未设置 `proxy` 时,使用系统环境代理设置(`HTTP_PROXY``HTTPS_PROXY``NO_PROXY`)。
#### 3) 启用工具发现的大规模 MCP 设置
*在此示例中LLM 只会看到 `tool_search_tool_bm25`。它将仅在用户请求时动态搜索并解锁 Github 或 Postgres 工具。*

View file

@ -899,6 +899,8 @@ type MCPServerConfig struct {
URL string `json:"url,omitempty"`
// Headers are HTTP headers to send with requests (sse/http only)
Headers map[string]string `json:"headers,omitempty"`
// Proxy is an optional proxy URL for SSE/HTTP transport (http/https/socks5/socks5h)
Proxy string `json:"proxy,omitempty"`
}
// MCPConfig defines configuration for all MCP servers

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
@ -290,6 +291,7 @@ func (m *Manager) ConnectServer(
"server": name,
"url": cfg.URL,
"disableStandaloneSSE": disableStandaloneSSE,
"proxy": cfg.Proxy,
})
sseTransport := &mcp.StreamableClientTransport{
@ -297,20 +299,45 @@ func (m *Manager) ConnectServer(
DisableStandaloneSSE: disableStandaloneSSE,
}
// Add custom headers if provided
if len(cfg.Headers) > 0 {
// Create a custom HTTP client with header-injecting transport
sseTransport.HTTPClient = &http.Client{
Transport: &headerTransport{
base: http.DefaultTransport,
headers: cfg.Headers,
// Create HTTP client with headers and optional proxy
needsCustomClient := len(cfg.Headers) > 0 || cfg.Proxy != ""
if needsCustomClient {
transport := &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
if cfg.Proxy != "" {
return url.Parse(cfg.Proxy)
}
// Fall back to environment proxy settings
return http.ProxyFromEnvironment(req)
},
}
logger.DebugCF("mcp", "Added custom HTTP headers",
map[string]any{
"server": name,
"header_count": len(cfg.Headers),
})
// Wrap with header transport if custom headers are provided
if len(cfg.Headers) > 0 {
sseTransport.HTTPClient = &http.Client{
Transport: &headerTransport{
base: transport,
headers: cfg.Headers,
},
}
logger.DebugCF("mcp", "Added custom HTTP headers",
map[string]any{
"server": name,
"header_count": len(cfg.Headers),
})
} else {
sseTransport.HTTPClient = &http.Client{
Transport: transport,
}
}
if cfg.Proxy != "" {
logger.DebugCF("mcp", "Using custom proxy for MCP server",
map[string]any{
"server": name,
"proxy": cfg.Proxy,
})
}
}
transport = sseTransport
@ -322,6 +349,7 @@ func (m *Manager) ConnectServer(
map[string]any{
"server": name,
"command": cfg.Command,
"proxy": cfg.Proxy,
})
// Create command with context
cmd := exec.CommandContext(ctx, cfg.Command, cfg.Args...)
@ -359,6 +387,22 @@ func (m *Manager) ConnectServer(
envMap[k] = v
}
// Set proxy environment variables for stdio transport
// This allows the subprocess to make HTTP requests through the proxy
if cfg.Proxy != "" {
envMap["HTTP_PROXY"] = cfg.Proxy
envMap["HTTPS_PROXY"] = cfg.Proxy
// By default, don't proxy localhost/loopback addresses
if _, exists := envMap["NO_PROXY"]; !exists {
envMap["NO_PROXY"] = "localhost,127.0.0.1,::1"
}
logger.DebugCF("mcp", "Added proxy environment variables for stdio transport",
map[string]any{
"server": name,
"proxy": cfg.Proxy,
})
}
// Convert map to slice
env := make([]string, 0, len(envMap))
for k, v := range envMap {