package mcp import ( "fmt" "net/http" "os" "os/exec" "strings" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" ) // newTransport 按 cfg 选传输。显式 TransportType 优先(stdio / sse / http,http 与 // streamable 等价,对齐 design/09 §2.1 的枚举);为空则沿用历史推断: // 有 Command 走 stdio,有 URL 走 SSE(既有 URL 配置多为 /sse 端点)。 // APIKey 通过自定义 RoundTripper 以 Authorization: Bearer 发送。 func newTransport(cfg MCPConfig) (mcpsdk.Transport, error) { switch cfg.TransportType { case "http", "streamable": return httpURLTransport(cfg, true) case "sse": return httpURLTransport(cfg, false) case "stdio": return commandTransport(cfg) } switch { case cfg.Command != "": return commandTransport(cfg) case cfg.URL != "": return httpURLTransport(cfg, false) default: return nil, fmt.Errorf("mcp: %w", ErrNoTransport) } } // transportNameOf 返回 cfg **实际会用到**的传输名(stdio / sse / http),与 // newTransport 的推断同源:显式 TransportType 优先(http / streamable 归一为 http), // 为空则「有 Command 走 stdio,有 URL 走 sse」。两者都没有时返回空串 —— 配置不成立, // 上游来源里该键缺席,而不是编一个 stdio。 // // 它服务于能力条目的来源章(mcpTool.Origin):面板要能写「来自 fs(stdio)」, // 而 cfg.TransportType 常常留空(由推断决定),因此不能直接下发原始配置值。 func transportNameOf(cfg MCPConfig) string { switch cfg.TransportType { case "http", "streamable": return "http" case "sse", "stdio": return cfg.TransportType } switch { case cfg.Command != "": return "stdio" case cfg.URL != "": return "sse" default: return "" } } // httpURLTransport 构建 URL 型传输:streamable=true 走 Streamable HTTP(2025-11-25 // 规范首选),否则走旧式 SSE。 func httpURLTransport(cfg MCPConfig, streamable bool) (mcpsdk.Transport, error) { if cfg.URL == "" { return nil, fmt.Errorf("mcp transport %s: %w", cfg.TransportType, ErrNoTransport) } client := httpClientFor(cfg) if streamable { return &mcpsdk.StreamableClientTransport{Endpoint: cfg.URL, HTTPClient: client}, nil } return &mcpsdk.SSEClientTransport{Endpoint: cfg.URL, HTTPClient: client}, nil } // commandTransport 拉起 stdio 子进程,按换行分隔的 JSON-RPC 通信。 func commandTransport(cfg MCPConfig) (mcpsdk.Transport, error) { if cfg.Command == "" { return nil, fmt.Errorf("mcp transport stdio: %w", ErrNoTransport) } cmd := exec.Command(cfg.Command, cfg.Args...) cmd.Env = envWith(os.Environ(), cfg.Env) return &mcpsdk.CommandTransport{Command: cmd}, nil } // httpClientFor 返回带鉴权头的 HTTP 客户端;没有 APIKey 时返回 nil(用 SDK 默认)。 func httpClientFor(cfg MCPConfig) *http.Client { if cfg.APIKey == "" { return nil } return &http.Client{Transport: &bearerTransport{base: http.DefaultTransport, token: cfg.APIKey}} } // bearerTransport 给每个请求加 Authorization: Bearer。凭据不写日志、不进错误消息。 type bearerTransport struct { base http.RoundTripper token string } // RoundTrip 实现 http.RoundTripper。 func (t *bearerTransport) RoundTrip(req *http.Request) (*http.Response, error) { clone := req.Clone(req.Context()) clone.Header.Set("Authorization", "Bearer "+t.token) return t.base.RoundTrip(clone) } // envWith 返回 base 叠加 extra 后的环境变量(同名以 extra 为准);extra 为空时原样返回。 func envWith(base []string, extra map[string]string) []string { if len(extra) == 0 { return base } out := make([]string, 0, len(base)+len(extra)) for _, kv := range base { if k, _, ok := strings.Cut(kv, "="); ok { if _, dup := extra[k]; dup { continue } } out = append(out, kv) } for k, v := range extra { out = append(out, k+"="+v) } return out }