diff --git a/pkg/errors/error.go b/pkg/errors/error.go index be2cf58f9..01789b708 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -3,6 +3,7 @@ package errors import ( "context" "fmt" + "net/http" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -44,10 +45,25 @@ func (e *GitHubGraphQLError) Error() string { return fmt.Errorf("%s: %w", e.Message, e.Err).Error() } +type GitHubRawAPIError struct { + Message string `json:"message"` + Response *http.Response `json:"-"` + Err error `json:"-"` +} + +func newGitHubRawAPIError(message string, resp *http.Response, err error) *GitHubRawAPIError { + return &GitHubRawAPIError{ + Message: message, + Response: resp, + Err: err, + } +} + type GitHubErrorKey struct{} type GitHubCtxErrors struct { api []*GitHubAPIError graphQL []*GitHubGraphQLError + raw []*GitHubRawAPIError } // ContextWithGitHubErrors updates or creates a context with a pointer to GitHub error information (to be used by middleware). @@ -59,6 +75,7 @@ func ContextWithGitHubErrors(ctx context.Context) context.Context { // If the context already has GitHubCtxErrors, we just empty the slices to start fresh val.api = []*GitHubAPIError{} val.graphQL = []*GitHubGraphQLError{} + val.raw = []*GitHubRawAPIError{} } else { // If not, we create a new GitHubCtxErrors and set it in the context ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{}) @@ -83,6 +100,14 @@ func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) return nil, fmt.Errorf("context does not contain GitHubCtxErrors") } +// GetGitHubRawAPIErrors retrieves the slice of GitHubRawAPIErrors from the context. +func GetGitHubRawAPIErrors(ctx context.Context) ([]*GitHubRawAPIError, error) { + if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { + return val.raw, nil // return the slice of raw API errors from the context + } + return nil, fmt.Errorf("context does not contain GitHubCtxErrors") +} + func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) (context.Context, error) { apiErr := newGitHubAPIError(message, resp, err) if ctx != nil { @@ -107,6 +132,15 @@ func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError return nil, fmt.Errorf("context does not contain GitHubCtxErrors") } +func addRawAPIErrorToContext(ctx context.Context, err *GitHubRawAPIError) (context.Context, error) { + if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { + val.raw = append(val.raw, err) + return ctx, nil + } + + return nil, fmt.Errorf("context does not contain GitHubCtxErrors") +} + // NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult { apiErr := newGitHubAPIError(message, resp, err) @@ -124,3 +158,11 @@ func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err erro } return utils.NewToolResultErrorFromErr(message, err) } + +func NewGitHubRawAPIErrorResponse(ctx context.Context, message string, resp *http.Response, err error) *mcp.CallToolResult { + rawAPIErr := newGitHubRawAPIError(message, resp, err) + if ctx != nil { + _, _ = addRawAPIErrorToContext(ctx, rawAPIErr) // Explicitly ignore error for graceful handling + } + return utils.NewToolResultErrorFromErr(message, err) +} diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index e5f6ec0c1..5066e9642 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -636,7 +636,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t } resp, err := rawClient.GetRawContent(ctx, owner, repo, path, rawOpts) if err != nil { - return utils.NewToolResultError("failed to get raw repository content"), nil, nil + return ghErrors.NewGitHubRawAPIErrorResponse(ctx, "failed to get raw repository content", resp, err), nil, nil } defer func() { _ = resp.Body.Close()