Skip to content
Open

Main #2001

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
119 changes: 53 additions & 66 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package github

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -715,86 +716,72 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool

if fileContent != nil && fileContent.SHA != nil {
fileSHA = *fileContent.SHA

rawClient, err := deps.GetRawClient(ctx)
fileSize := fileContent.GetSize()
// Build resource URI for the file using URI templates
pathParts := strings.Split(path, "/")
resourceURI, err := expandRepoResourceURI(owner, repo, sha, ref, pathParts)
if err != nil {
return utils.NewToolResultError("failed to get GitHub raw content client"), nil, nil
return nil, nil, fmt.Errorf("failed to create resource URI: %w", err)
}
resp, err := rawClient.GetRawContent(ctx, owner, repo, path, rawOpts)
if err != nil {
return utils.NewToolResultError("failed to get raw repository content"), nil, nil

// main branch ref passed in ref parameter but it doesn't exist - default branch was used
var successNote string
if fallbackUsed {
successNote = fmt.Sprintf(" Note: the provided ref '%s' does not exist, default branch '%s' was used instead.", originalRef, rawOpts.Ref)
}
defer func() {
_ = resp.Body.Close()
}()

if resp.StatusCode == http.StatusOK {
// If the raw content is found, return it directly
body, err := io.ReadAll(resp.Body)
if err != nil {
return ghErrors.NewGitHubRawAPIErrorResponse(ctx, "failed to get raw repository content", resp, err), nil, 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, 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, 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, nil, fmt.Errorf("failed to create resource URI: %w", err)
}
// For files >= 1MB, return a ResourceLink instead of content
const maxContentSize = 1024 * 1024 // 1MB
if fileSize >= maxContentSize {
size := int64(fileSize)
resourceLink := &mcp.ResourceLink{
URI: resourceURI,
Name: fileContent.GetName(),
Title: fmt.Sprintf("File: %s", path),
Size: &size,
}
return utils.NewToolResultResourceLink(
fmt.Sprintf("File %s is too large to display (%d bytes). Use the download URL to fetch the content: %s (SHA: %s)%s",
path, fileSize, fileContent.GetDownloadURL(), fileSHA, successNote),
resourceLink), nil, nil
}

// main branch ref passed in ref parameter but it doesn't exist - default branch was used
var successNote string
if fallbackUsed {
successNote = fmt.Sprintf(" Note: the provided ref '%s' does not exist, default branch '%s' was used instead.", originalRef, rawOpts.Ref)
}
// For files < 1MB, get content directly from Contents API
content, err := fileContent.GetContent()
if err != nil {
return utils.NewToolResultError(fmt.Sprintf("failed to decode file content: %s", err)), nil, nil
}

// Determine if content is text or binary
isTextContent := strings.HasPrefix(contentType, "text/") ||
contentType == "application/json" ||
contentType == "application/xml" ||
strings.HasSuffix(contentType, "+json") ||
strings.HasSuffix(contentType, "+xml")

if isTextContent {
result := &mcp.ResourceContents{
URI: resourceURI,
Text: string(body),
MIMEType: contentType,
}
// Include SHA in the result metadata
if fileSHA != "" {
return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)", fileSHA)+successNote, result), nil, nil
}
return utils.NewToolResultResource("successfully downloaded text file"+successNote, result), nil, nil
}
// Detect content type from the actual content bytes,
// mirroring the original approach of using the Content-Type header
// from the raw API response.
contentBytes := []byte(content)
contentType := http.DetectContentType(contentBytes)

// Determine if content is text or binary based on detected content type
isTextContent := strings.HasPrefix(contentType, "text/") ||
contentType == "application/json" ||
contentType == "application/xml" ||
strings.HasSuffix(contentType, "+json") ||
strings.HasSuffix(contentType, "+xml")

if isTextContent {
result := &mcp.ResourceContents{
URI: resourceURI,
Blob: body,
Text: content,
MIMEType: contentType,
}
// Include SHA in the result metadata
if fileSHA != "" {
return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)", fileSHA)+successNote, result), nil, nil
}
return utils.NewToolResultResource("successfully downloaded binary file"+successNote, result), nil, nil
return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)%s", fileSHA, successNote), result), nil, nil
}

// Raw API call failed
return matchFiles(ctx, client, owner, repo, ref, path, rawOpts, resp.StatusCode)
// Binary content - encode as base64 blob
blobContent := base64.StdEncoding.EncodeToString(contentBytes)
result := &mcp.ResourceContents{
URI: resourceURI,
Blob: []byte(blobContent),
MIMEType: contentType,
}
return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)%s", fileSHA, successNote), result), nil, nil
} else if dirContent != nil {
// file content or file SHA is nil which means it's a directory
r, err := json.Marshal(dirContent)
Expand Down
Loading
Loading