@@ -2,11 +2,15 @@ package github
22
33import (
44"context"
5+ "encoding/base64"
56"encoding/json"
67"fmt"
78"io"
89"net/http"
10+ "net/url"
11+ "strings"
912
13+ "github.com/github/github-mcp-server/pkg/raw"
1014"github.com/github/github-mcp-server/pkg/translations"
1115"github.com/google/go-github/v72/github"
1216"github.com/mark3labs/mcp-go/mcp"
@@ -409,7 +413,7 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun
409413}
410414
411415// GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository.
412- func GetFileContents (getClient GetClientFn ,t translations.TranslationHelperFunc ) (tool mcp.Tool ,handler server.ToolHandlerFunc ) {
416+ func GetFileContents (getClient GetClientFn ,getRawClient raw. GetRawClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool ,handler server.ToolHandlerFunc ) {
413417return mcp .NewTool ("get_file_contents" ,
414418mcp .WithDescription (t ("TOOL_GET_FILE_CONTENTS_DESCRIPTION" ,"Get the contents of a file or directory from a GitHub repository" )),
415419mcp .WithToolAnnotation (mcp.ToolAnnotation {
@@ -426,7 +430,7 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
426430),
427431mcp .WithString ("path" ,
428432mcp .Required (),
429- mcp .Description ("Path to file/directory" ),
433+ mcp .Description ("Path to file/directory (directories must end with a slash '/') " ),
430434),
431435mcp .WithString ("branch" ,
432436mcp .Description ("Branch to get contents from" ),
@@ -450,38 +454,92 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
450454return mcp .NewToolResultError (err .Error ()),nil
451455}
452456
453- client ,err := getClient (ctx )
454- if err != nil {
455- return nil ,fmt .Errorf ("failed to get GitHub client: %w" ,err )
457+ // If the path is (most likely) not to be a directory, we will first try to get the raw content from the GitHub raw content API.
458+ if path != "" && ! strings .HasSuffix (path ,"/" ) {
459+ rawOpts := & raw.RawContentOpts {}
460+ if branch != "" {
461+ rawOpts .Ref = "refs/heads/" + branch
462+ }
463+ rawClient ,err := getRawClient (ctx )
464+ if err != nil {
465+ return mcp .NewToolResultError ("failed to get GitHub raw content client" ),nil
466+ }
467+ resp ,err := rawClient .GetRawContent (ctx ,owner ,repo ,path ,rawOpts )
468+ if err != nil {
469+ return mcp .NewToolResultError ("failed to get raw repository content" ),nil
470+ }
471+ defer func () {
472+ _ = resp .Body .Close ()
473+ }()
474+
475+ if resp .StatusCode != http .StatusOK {
476+ // If the raw content is not found, we will fall back to the GitHub API (in case it is a directory)
477+ }else {
478+ // If the raw content is found, return it directly
479+ body ,err := io .ReadAll (resp .Body )
480+ if err != nil {
481+ return mcp .NewToolResultError ("failed to read response body" ),nil
482+ }
483+ contentType := resp .Header .Get ("Content-Type" )
484+
485+ var resourceURI string
486+ if branch == "" {
487+ // do a safe url join
488+ resourceURI ,err = url .JoinPath ("repo://" ,owner ,repo ,"contents" ,path )
489+ if err != nil {
490+ return nil ,fmt .Errorf ("failed to create resource URI: %w" ,err )
491+ }
492+ }else {
493+ resourceURI ,err = url .JoinPath ("repo://" ,owner ,repo ,"refs" ,"heads" ,branch ,"contents" ,path )
494+ if err != nil {
495+ return nil ,fmt .Errorf ("failed to create resource URI: %w" ,err )
496+ }
497+ }
498+ if strings .HasPrefix (contentType ,"application" )|| strings .HasPrefix (contentType ,"text" ) {
499+ return mcp .NewToolResultResource ("successfully downloaded text file" , mcp.TextResourceContents {
500+ URI :resourceURI ,
501+ Text :string (body ),
502+ MIMEType :contentType ,
503+ }),nil
504+ }
505+
506+ return mcp .NewToolResultResource ("successfully downloaded binary file" , mcp.BlobResourceContents {
507+ URI :resourceURI ,
508+ Blob :base64 .StdEncoding .EncodeToString (body ),
509+ MIMEType :contentType ,
510+ }),nil
511+
512+ }
456513}
457- opts := & github. RepositoryContentGetOptions { Ref : branch }
458- fileContent , dirContent , resp , err := client . Repositories . GetContents (ctx , owner , repo , path , opts )
514+
515+ client , err := getClient (ctx )
459516if err != nil {
460- return nil , fmt . Errorf ("failed to getfile contents: %w" , err )
517+ return mcp . NewToolResultError ("failed to getGitHub client" ), nil
461518}
462- defer func () {_ = resp .Body .Close () }()
463519
464- if resp .StatusCode != 200 {
465- body ,err := io .ReadAll (resp .Body )
520+ if strings .HasSuffix (path ,"/" ) {
521+ opts := & github.RepositoryContentGetOptions {Ref :branch }
522+ _ ,dirContent ,resp ,err := client .Repositories .GetContents (ctx ,owner ,repo ,path ,opts )
466523if err != nil {
467- return nil , fmt . Errorf ("failed toread response body: %w" , err )
524+ return mcp . NewToolResultError ("failed toget file contents" ), nil
468525}
469- return mcp .NewToolResultError (fmt .Sprintf ("failed to get file contents: %s" ,string (body ))),nil
470- }
526+ defer func () {_ = resp .Body .Close () }()
471527
472- var result interface {}
473- if fileContent != nil {
474- result = fileContent
475- }else {
476- result = dirContent
477- }
528+ if resp .StatusCode != 200 {
529+ body ,err := io .ReadAll (resp .Body )
530+ if err != nil {
531+ return mcp .NewToolResultError ("failed to read response body" ),nil
532+ }
533+ return mcp .NewToolResultError (fmt .Sprintf ("failed to get file contents: %s" ,string (body ))),nil
534+ }
478535
479- r ,err := json .Marshal (result )
480- if err != nil {
481- return nil ,fmt .Errorf ("failed to marshal response: %w" ,err )
536+ r ,err := json .Marshal (dirContent )
537+ if err != nil {
538+ return mcp .NewToolResultError ("failed to marshal response" ),nil
539+ }
540+ return mcp .NewToolResultText (string (r )),nil
482541}
483-
484- return mcp .NewToolResultText (string (r )),nil
542+ return mcp .NewToolResultError ("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository." ),nil
485543}
486544}
487545