package mcpserver import ( "bytes" "context" "fmt" "io" "net/http" "net/url" "strings" "time" ) const ( requestTimeout = 60 * time.Second maxResponseSize = 8 << 20 ) type Client struct { baseURL string apiKey string http *http.Client } type Response struct { Status int Body string } func NewClient(baseURL string, apiKey string, httpClient *http.Client) *Client { if httpClient == nil { httpClient = &http.Client{Timeout: requestTimeout} } return &Client{baseURL: strings.TrimRight(baseURL, "/"), apiKey: apiKey, http: httpClient} } func (c *Client) Do(ctx context.Context, method string, path string, query url.Values, body []byte, contentType string) (Response, error) { target := c.baseURL + path if len(query) > 0 { target += "?" + query.Encode() } req, err := http.NewRequestWithContext(ctx, method, target, bytes.NewReader(body)) if err != nil { return Response{}, err } if c.apiKey != "" { req.Header.Set("Authorization", "Bearer "+c.apiKey) } if contentType != "" { req.Header.Set("Content-Type", contentType) } res, err := c.http.Do(req) if err != nil { return Response{}, err } defer res.Body.Close() raw, err := io.ReadAll(io.LimitReader(res.Body, maxResponseSize)) if err != nil { return Response{}, err } return Response{Status: res.StatusCode, Body: string(raw)}, nil } func (r Response) Failed() bool { return r.Status < 200 || r.Status > 299 } func (r Response) Describe() string { text := strings.TrimSpace(r.Body) if r.Status == http.StatusNoContent || text == "" { return fmt.Sprintf("HTTP %d", r.Status) } if r.Failed() { return fmt.Sprintf("HTTP %d: %s", r.Status, text) } return text }