From 4ab833ca7b79043a67f42163807054d82888aefb Mon Sep 17 00:00:00 2001 From: uiyzzi Date: Wed, 1 Apr 2026 18:52:40 +0800 Subject: [PATCH] Add proxy support for MCP server connections --- docs/tools_configuration.md | 31 +++++++++++++++- docs/zh/tools_configuration.md | 31 +++++++++++++++- pkg/config/config.go | 2 + pkg/mcp/manager.go | 68 ++++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 16 deletions(-) diff --git a/docs/tools_configuration.md b/docs/tools_configuration.md index 5a4b5bb28..98f539899 100644 --- a/docs/tools_configuration.md +++ b/docs/tools_configuration.md @@ -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 diff --git a/docs/zh/tools_configuration.md b/docs/zh/tools_configuration.md index 63ac5000b..af09d5a7b 100644 --- a/docs/zh/tools_configuration.md +++ b/docs/zh/tools_configuration.md @@ -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 工具。* diff --git a/pkg/config/config.go b/pkg/config/config.go index 7a11d1ab7..730562d16 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 diff --git a/pkg/mcp/manager.go b/pkg/mcp/manager.go index 323df0312..49ef66819 100644 --- a/pkg/mcp/manager.go +++ b/pkg/mcp/manager.go @@ -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 {