diff --git a/pkg/errors/constants.go b/pkg/errors/constants.go new file mode 100644 index 000000000..ced41c7d3 --- /dev/null +++ b/pkg/errors/constants.go @@ -0,0 +1,7 @@ +package errors + +const ( + ErrContextMissingGitHubCtxErrors = "context does not contain GitHubCtxErrors" + ErrFailedToGetGitHubClient = "failed to get GitHub client" + ErrMissingRequiredParameter = "missing required parameter: %s" +) diff --git a/pkg/errors/error.go b/pkg/errors/error.go index 9d81e9010..dde2cdaf6 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -2,6 +2,7 @@ package errors import ( "context" + "errors" "fmt" "github.com/google/go-github/v72/github" @@ -71,7 +72,7 @@ func GetGitHubAPIErrors(ctx context.Context) ([]*GitHubAPIError, error) { if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { return val.api, nil // return the slice of API errors from the context } - return nil, fmt.Errorf("context does not contain GitHubCtxErrors") + return nil, errors.New(ErrContextMissingGitHubCtxErrors) } // GetGitHubGraphQLErrors retrieves the slice of GitHubGraphQLErrors from the context. @@ -79,7 +80,7 @@ func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { return val.graphQL, nil // return the slice of GraphQL errors from the context } - return nil, fmt.Errorf("context does not contain GitHubCtxErrors") + return nil, errors.New(ErrContextMissingGitHubCtxErrors) } func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) (context.Context, error) { @@ -95,7 +96,7 @@ func addGitHubAPIErrorToContext(ctx context.Context, err *GitHubAPIError) (conte val.api = append(val.api, err) // append the error to the existing slice in the context return ctx, nil } - return nil, fmt.Errorf("context does not contain GitHubCtxErrors") + return nil, errors.New(ErrContextMissingGitHubCtxErrors) } func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError) (context.Context, error) { @@ -103,7 +104,7 @@ func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError val.graphQL = append(val.graphQL, err) // append the error to the existing slice in the context return ctx, nil } - return nil, fmt.Errorf("context does not contain GitHubCtxErrors") + return nil, errors.New(ErrContextMissingGitHubCtxErrors) } // NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 29f776a05..b64f458fb 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -446,6 +446,36 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun } } +// buildResourceURI constructs a resource URI based on the provided owner, repo, path, sha, and ref. +// This helper reduces cognitive complexity by centralizing URI construction logic. +func buildResourceURI(owner, repo, path, sha, ref string) (string, error) { + switch { + case sha != "": + return url.JoinPath("repo://", owner, repo, "sha", sha, "contents", path) + case ref != "": + return url.JoinPath("repo://", owner, repo, ref, "contents", path) + default: + return url.JoinPath("repo://", owner, repo, "contents", path) + } +} + +// createResourceContent creates an MCP resource content based on the content type. +// Returns a TextResourceContents for text/application types, BlobResourceContents for binary. +func createResourceContent(body []byte, contentType, resourceURI string) mcp.ResourceContents { + if strings.HasPrefix(contentType, "application") || strings.HasPrefix(contentType, "text") { + return mcp.TextResourceContents{ + URI: resourceURI, + Text: string(body), + MIMEType: contentType, + } + } + return mcp.BlobResourceContents{ + URI: resourceURI, + Blob: base64.StdEncoding.EncodeToString(body), + MIMEType: contentType, + } +} + // GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository. func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_file_contents", @@ -537,46 +567,22 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t }() if resp.StatusCode == http.StatusOK { - // If the raw content is found, return it directly body, err := io.ReadAll(resp.Body) if err != nil { return mcp.NewToolResultError("failed to read response body"), nil } contentType := resp.Header.Get("Content-Type") - var resourceURI string - switch { - case sha != "": - resourceURI, err = url.JoinPath("repo://", owner, repo, "sha", sha, "contents", path) - if err != nil { - return nil, fmt.Errorf("failed to create resource URI: %w", err) - } - case ref != "": - resourceURI, err = url.JoinPath("repo://", owner, repo, ref, "contents", path) - if err != nil { - return nil, fmt.Errorf("failed to create resource URI: %w", err) - } - default: - resourceURI, err = url.JoinPath("repo://", owner, repo, "contents", path) - if err != nil { - return nil, fmt.Errorf("failed to create resource URI: %w", err) - } + resourceURI, err := buildResourceURI(owner, repo, path, sha, ref) + if err != nil { + return nil, fmt.Errorf("failed to create resource URI: %w", err) } - if strings.HasPrefix(contentType, "application") || strings.HasPrefix(contentType, "text") { - return mcp.NewToolResultResource("successfully downloaded text file", mcp.TextResourceContents{ - URI: resourceURI, - Text: string(body), - MIMEType: contentType, - }), nil + resourceContent := createResourceContent(body, contentType, resourceURI) + if _, isText := resourceContent.(mcp.TextResourceContents); isText { + return mcp.NewToolResultResource("successfully downloaded text file", resourceContent), nil } - - return mcp.NewToolResultResource("successfully downloaded binary file", mcp.BlobResourceContents{ - URI: resourceURI, - Blob: base64.StdEncoding.EncodeToString(body), - MIMEType: contentType, - }), nil - + return mcp.NewToolResultResource("successfully downloaded binary file", resourceContent), nil } }