@@ -16,8 +16,13 @@ import (
1616 "github.com/google/go-github/v72/github"
1717 "github.com/mark3labs/mcp-go/mcp"
1818 "github.com/mark3labs/mcp-go/server"
19+ "github.com/shurcooL/githubv4"
1920)
2021
22+ // getFileSHAFunc is a package-level variable that holds the getFileSHA function
23+ // This allows tests to mock the behavior
24+ var getFileSHAFunc = getFileSHA
25+
2126func GetCommit (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
2227 return mcp .NewTool ("get_commit" ,
2328 mcp .WithDescription (t ("TOOL_GET_COMMITS_DESCRIPTION" , "Get details for a commit from a GitHub repository" )),
@@ -446,7 +451,7 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun
446451}
447452
448453// GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository.
449- func GetFileContents (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
454+ func GetFileContents (getClient GetClientFn , getRawClient raw.GetRawClientFn , getGQLClient GetGQLClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
450455 return mcp .NewTool ("get_file_contents" ,
451456 mcp .WithDescription (t ("TOOL_GET_FILE_CONTENTS_DESCRIPTION" , "Get the contents of a file or directory from a GitHub repository" )),
452457 mcp .WithToolAnnotation (mcp.ToolAnnotation {
@@ -507,15 +512,13 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
507512 // If the path is (most likely) not to be a directory, we will
508513 // first try to get the raw content from the GitHub raw content API.
509514 if path != "" && ! strings .HasSuffix (path , "/" ) {
510- // First, get file info from Contents API to retrieve SHA
511- var fileSHA string
512- opts := & github.RepositoryContentGetOptions {Ref : ref }
513- fileContent , _ , respContents , err := client .Repositories .GetContents (ctx , owner , repo , path , opts )
514- if respContents != nil {
515- defer func () { _ = respContents .Body .Close () }()
515+ gqlClient , err := getGQLClient (ctx )
516+ if err != nil {
517+ return mcp .NewToolResultError ("failed to get GitHub GraphQL client" ), nil
516518 }
517- if err == nil && respContents .StatusCode == http .StatusOK && fileContent != nil && fileContent .SHA != nil {
518- fileSHA = * fileContent .SHA
519+ fileSHA , err := getFileSHAFunc (ctx , gqlClient , owner , repo , path , rawOpts )
520+ if err != nil {
521+ return mcp .NewToolResultError (fmt .Sprintf ("failed to get file SHA: %s" , err )), nil
519522 }
520523
521524 rawClient , err := getRawClient (ctx )
@@ -1383,3 +1386,34 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner
13831386 // Use provided ref, or it will be empty which defaults to the default branch
13841387 return & raw.ContentOpts {Ref : ref , SHA : sha }, nil
13851388}
1389+
1390+ // getFileSHA retrieves the Blob SHA of a file.
1391+ func getFileSHA (ctx context.Context , client * githubv4.Client , owner , repo , path string , opts * raw.ContentOpts ) (string , error ) {
1392+ var query struct {
1393+ Repository struct {
1394+ Object struct {
1395+ Blob struct {
1396+ OID string
1397+ } `graphql:"... on Blob"`
1398+ } `graphql:"object(expression: $expression)"`
1399+ } `graphql:"repository(owner: $owner, name: $repo)"`
1400+ }
1401+
1402+ // Prepare the expression based on the provided options
1403+ expression := githubv4 .String (path )
1404+ if opts != nil && opts .SHA != "" {
1405+ expression = githubv4 .String (fmt .Sprintf ("%s:%s" , opts .SHA , path ))
1406+ } else if opts != nil && opts .Ref != "" {
1407+ expression = githubv4 .String (fmt .Sprintf ("%s:%s" , opts .Ref , path ))
1408+ }
1409+
1410+ variables := map [string ]interface {}{
1411+ "owner" : githubv4 .String (owner ),
1412+ "repo" : githubv4 .String (repo ),
1413+ "expression" : expression ,
1414+ }
1415+ if err := client .Query (ctx , & query , variables ); err != nil {
1416+ return "" , fmt .Errorf ("failed to query file SHA: %w" , err )
1417+ }
1418+ return query .Repository .Object .Blob .OID , nil
1419+ }
0 commit comments