connectOne establishes a connection to a single MCP server, discovers its tools, and wraps each one as an AgentTool with the server slug prefix applied.
( ctx context.Context, logger slog.Logger, cfg database.MCPServerConfig, tokensByConfigID map[uuid.UUID]database.MCPServerUserToken, userID uuid.UUID, oidcSrc UserOIDCTokenSource, coderHeaders map[string]string, )
| 170 | // discovers its tools, and wraps each one as an AgentTool with |
| 171 | // the server slug prefix applied. |
| 172 | func connectOne( |
| 173 | ctx context.Context, |
| 174 | logger slog.Logger, |
| 175 | cfg database.MCPServerConfig, |
| 176 | tokensByConfigID map[uuid.UUID]database.MCPServerUserToken, |
| 177 | userID uuid.UUID, |
| 178 | oidcSrc UserOIDCTokenSource, |
| 179 | coderHeaders map[string]string, |
| 180 | ) ([]fantasy.AgentTool, *client.Client, error) { |
| 181 | headers := buildAuthHeaders(ctx, logger, cfg, tokensByConfigID, userID, oidcSrc) |
| 182 | |
| 183 | // When opted-in, merge Coder identity headers BEFORE the |
| 184 | // transport is created so any auth header already set above |
| 185 | // wins on a conflict. Conflict detection uses |
| 186 | // http.CanonicalHeaderKey because the upstream transport applies |
| 187 | // http.Header.Set, which canonicalizes keys; without that, an |
| 188 | // admin-configured header that differs only in case from a Coder |
| 189 | // identity header would land in the request map twice and the |
| 190 | // surviving value would be non-deterministic. |
| 191 | if cfg.ForwardCoderHeaders { |
| 192 | canonicalAuth := make(map[string]struct{}, len(headers)) |
| 193 | for k := range headers { |
| 194 | canonicalAuth[http.CanonicalHeaderKey(k)] = struct{}{} |
| 195 | } |
| 196 | for k, v := range coderHeaders { |
| 197 | if _, exists := canonicalAuth[http.CanonicalHeaderKey(k)]; exists { |
| 198 | continue |
| 199 | } |
| 200 | headers[k] = v |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | tr, err := createTransport(cfg, headers) |
| 205 | if err != nil { |
| 206 | return nil, nil, xerrors.Errorf( |
| 207 | "create transport: %w", err, |
| 208 | ) |
| 209 | } |
| 210 | |
| 211 | mcpClient := client.NewClient(tr) |
| 212 | |
| 213 | // The timeout covers the entire connect+init+list sequence, |
| 214 | // not each phase individually. |
| 215 | connectCtx, cancel := context.WithTimeout( |
| 216 | ctx, connectTimeout, |
| 217 | ) |
| 218 | defer cancel() |
| 219 | |
| 220 | if err := mcpClient.Start(connectCtx); err != nil { |
| 221 | _ = mcpClient.Close() |
| 222 | return nil, nil, xerrors.Errorf( |
| 223 | "start transport: %w", err, |
| 224 | ) |
| 225 | } |
| 226 | |
| 227 | _, err = mcpClient.Initialize( |
| 228 | connectCtx, |
| 229 | mcp.InitializeRequest{ |
no test coverage detected