package woodpecker import ( "encoding/json" "fmt" ) // APIError represents a failure returned by the Woodpecker API or an // unrecoverable error while communicating with it. The Status field is an // HTTP status code when the failure came from the API, or 500 for transport // and decoding errors, mirroring the behaviour of the original client. type APIError struct { Status int Message string } func (e *APIError) Error() string { if e.Message == "" { return fmt.Sprintf("woodpecker api error (status %d)", e.Status) } return fmt.Sprintf("woodpecker api error (status %d): %s", e.Status, e.Message) } // errorContext is the structured payload returned to the MCP client when a // tool call fails. The shape is kept identical to the original TypeScript // implementation so existing MCP clients see the same output. type errorContext struct { Description string `json:"description"` Suggestions []string `json:"suggestions"` Error string `json:"error"` } // ToContext renders a client-facing error context for the given operation. // The status code drives both the description and the suggested next steps. func (e *APIError) ToContext(context string) string { description := "Request failed for " + context if e.Message != "" { description += ": " + e.Message } ctx := errorContext{ Description: description, Suggestions: []string{ "Check your network connection", "Verify the Woodpecker CI server URL is correct", "Try again in a few moments", }, Error: "UNKNOWN_ERROR", } switch e.Status { case 401: ctx = errorContext{ Description: "Authentication failed for " + context, Suggestions: []string{ "Check that your WOODPECKER_TOKEN environment variable is set correctly", "Verify the token has not expired", "Ensure the token has sufficient permissions", }, Error: "AUTH_ERROR", } case 403: ctx = errorContext{ Description: "Access denied for " + context, Suggestions: []string{ "Check that you have permission to access this resource", "Verify the repository/pipeline exists and is accessible", "Contact your Woodpecker CI administrator if needed", }, Error: "PERMISSION_ERROR", } case 404: ctx = errorContext{ Description: "Resource not found for " + context, Suggestions: []string{ "Double-check the repository name, pipeline number, or step ID", "Verify the resource exists and hasn't been deleted", }, Error: "NOT_FOUND", } case 429: ctx = errorContext{ Description: "Rate limit exceeded for " + context, Suggestions: []string{ "Wait a moment before retrying", "Consider reducing the frequency of requests", }, Error: "RATE_LIMIT", } case 500, 502, 503, 504: ctx = errorContext{ Description: "Woodpecker CI server error for " + context, Suggestions: []string{ "Try again in a few moments", "Check Woodpecker CI server status", "Contact your administrator if the issue persists", }, Error: "SERVER_ERROR", } } // Marshalling a plain struct never fails; ignore the error defensively. b, _ := json.Marshal(ctx) return string(b) }