Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/errors/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package errors

const (
ErrContextMissingGitHubCtxErrors = "context does not contain GitHubCtxErrors"
ErrFailedToGetGitHubClient = "failed to get GitHub client"
ErrMissingRequiredParameter = "missing required parameter: %s"
)
9 changes: 5 additions & 4 deletions pkg/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package errors

import (
"context"
"errors"
"fmt"

"github.com/google/go-github/v72/github"
Expand Down Expand Up @@ -71,15 +72,15 @@ 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.
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) {
Expand All @@ -95,15 +96,15 @@ 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) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
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
Expand Down
68 changes: 37 additions & 31 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
}
}

Expand Down