@@ -2,11 +2,15 @@ package github
2
2
3
3
import (
4
4
"context"
5
+ "encoding/base64"
5
6
"encoding/json"
6
7
"fmt"
7
8
"io"
8
9
"net/http"
10
+ "net/url"
11
+ "strings"
9
12
13
+ "github.com/github/github-mcp-server/pkg/raw"
10
14
"github.com/github/github-mcp-server/pkg/translations"
11
15
"github.com/google/go-github/v72/github"
12
16
"github.com/mark3labs/mcp-go/mcp"
@@ -409,7 +413,7 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun
409
413
}
410
414
411
415
// 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 ) {
413
417
return mcp .NewTool ("get_file_contents" ,
414
418
mcp .WithDescription (t ("TOOL_GET_FILE_CONTENTS_DESCRIPTION" ,"Get the contents of a file or directory from a GitHub repository" )),
415
419
mcp .WithToolAnnotation (mcp.ToolAnnotation {
@@ -426,7 +430,7 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
426
430
),
427
431
mcp .WithString ("path" ,
428
432
mcp .Required (),
429
- mcp .Description ("Path to file/directory" ),
433
+ mcp .Description ("Path to file/directory (directories must end with a slash '/') " ),
430
434
),
431
435
mcp .WithString ("branch" ,
432
436
mcp .Description ("Branch to get contents from" ),
@@ -450,38 +454,92 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
450
454
return mcp .NewToolResultError (err .Error ()),nil
451
455
}
452
456
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
+ }
456
513
}
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 )
459
516
if err != nil {
460
- return nil , fmt . Errorf ("failed to getfile contents: %w" , err )
517
+ return mcp . NewToolResultError ("failed to getGitHub client" ), nil
461
518
}
462
- defer func () {_ = resp .Body .Close () }()
463
519
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 )
466
523
if err != nil {
467
- return nil , fmt . Errorf ("failed toread response body: %w" , err )
524
+ return mcp . NewToolResultError ("failed toget file contents" ), nil
468
525
}
469
- return mcp .NewToolResultError (fmt .Sprintf ("failed to get file contents: %s" ,string (body ))),nil
470
- }
526
+ defer func () {_ = resp .Body .Close () }()
471
527
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
+ }
478
535
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
482
541
}
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
485
543
}
486
544
}
487
545