woodpecker-mcp/internal/woodpecker/errors.go
2026-08-08 01:37:47 +03:00

104 lines
3.1 KiB
Go

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)
}