@@ -3,7 +3,11 @@ package github
33import (
44 "context"
55 "encoding/base64"
6+ "errors"
7+ "fmt"
8+ "io"
69 "mime"
10+ "net/http"
711 "path/filepath"
812 "strings"
913
@@ -13,110 +17,185 @@ import (
1317 "github.com/mark3labs/mcp-go/server"
1418)
1519
16- // getRepositoryContent defines the resource template and handler for the Repository Content API.
17- func getRepositoryContent (client * github.Client , t translations.TranslationHelperFunc ) (mainTemplate mcp.ResourceTemplate , reftemplate mcp.ResourceTemplate , shaTemplate mcp.ResourceTemplate , tagTemplate mcp.ResourceTemplate , prTemplate mcp.ResourceTemplate , handler server.ResourceTemplateHandlerFunc ) {
18-
20+ // getRepositoryResourceContent defines the resource template and handler for getting repository content.
21+ func getRepositoryResourceContent (client * github.Client , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
1922 return mcp .NewResourceTemplate (
2023 "repo://{owner}/{repo}/contents{/path*}" , // Resource template
2124 t ("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION" , "Repository Content" ),
22- ), mcp .NewResourceTemplate (
25+ ),
26+ repositoryResourceContentsHandler (client )
27+ }
28+
29+ // getRepositoryContent defines the resource template and handler for getting repository content for a branch.
30+ func getRepositoryResourceBranchContent (client * github.Client , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
31+ return mcp .NewResourceTemplate (
2332 "repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}" , // Resource template
2433 t ("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION" , "Repository Content for specific branch" ),
25- ), mcp .NewResourceTemplate (
34+ ),
35+ repositoryResourceContentsHandler (client )
36+ }
37+
38+ // getRepositoryResourceCommitContent defines the resource template and handler for getting repository content for a commit.
39+ func getRepositoryResourceCommitContent (client * github.Client , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
40+ return mcp .NewResourceTemplate (
2641 "repo://{owner}/{repo}/sha/{sha}/contents{/path*}" , // Resource template
2742 t ("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION" , "Repository Content for specific commit" ),
28- ), mcp .NewResourceTemplate (
43+ ),
44+ repositoryResourceContentsHandler (client )
45+ }
46+
47+ // getRepositoryResourceTagContent defines the resource template and handler for getting repository content for a tag.
48+ func getRepositoryResourceTagContent (client * github.Client , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
49+ return mcp .NewResourceTemplate (
2950 "repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}" , // Resource template
3051 t ("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION" , "Repository Content for specific tag" ),
31- ), mcp .NewResourceTemplate (
52+ ),
53+ repositoryResourceContentsHandler (client )
54+ }
55+
56+ // getRepositoryResourcePrContent defines the resource template and handler for getting repository content for a pull request.
57+ func getRepositoryResourcePrContent (client * github.Client , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
58+ return mcp .NewResourceTemplate (
3259 "repo://{owner}/{repo}/refs/pull/{pr_number}/head/contents{/path*}" , // Resource template
3360 t ("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION" , "Repository Content for specific pull request" ),
34- ), func (ctx context.Context , request mcp.ReadResourceRequest ) ([]mcp.ResourceContents , error ) {
35- // Extract parameters from request.Params.URI
61+ ),
62+ repositoryResourceContentsHandler (client )
63+ }
3664
37- owner := request .Params .Arguments ["owner" ].([]string )[0 ]
38- repo := request .Params .Arguments ["repo" ].([]string )[0 ]
39- // path should be a joined list of the path parts
40- path := strings .Join (request .Params .Arguments ["path" ].([]string ), "/" )
65+ func repositoryResourceContentsHandler (client * github.Client ) func (ctx context.Context , request mcp.ReadResourceRequest ) ([]mcp.ResourceContents , error ) {
66+ return func (ctx context.Context , request mcp.ReadResourceRequest ) ([]mcp.ResourceContents , error ) {
67+ // the matcher will give []string with one elemenent
68+ // https://github.com/mark3labs/mcp-go/pull/54
69+ o , ok := request .Params .Arguments ["owner" ].([]string )
70+ if ! ok || len (o ) == 0 {
71+ return nil , errors .New ("owner is required" )
72+ }
73+ owner := o [0 ]
4174
42- opts := & github.RepositoryContentGetOptions {}
75+ r , ok := request .Params .Arguments ["repo" ].([]string )
76+ if ! ok || len (r ) == 0 {
77+ return nil , errors .New ("repo is required" )
78+ }
79+ repo := r [0 ]
4380
44- sha , ok := request .Params .Arguments ["sha" ].([]string )
45- if ok {
46- opts .Ref = sha [0 ]
47- }
81+ // path should be a joined list of the path parts
82+ path := ""
83+ p , ok := request .Params .Arguments ["path" ].([]string )
84+ if ok {
85+ path = strings .Join (p , "/" )
86+ }
4887
49- branch , ok := request .Params .Arguments ["branch" ].([]string )
50- if ok {
51- opts .Ref = "refs/heads/" + branch [0 ]
52- }
88+ opts := & github.RepositoryContentGetOptions {}
5389
54- tag , ok := request .Params .Arguments ["tag" ].([]string )
55- if ok {
56- opts .Ref = "refs/tags/" + tag [0 ]
57- }
58- prNumber , ok := request .Params .Arguments ["pr_number" ].([]string )
59- if ok {
60- opts .Ref = "refs/pull/" + prNumber [0 ] + "/head"
61- }
90+ sha , ok := request .Params .Arguments ["sha" ].([]string )
91+ if ok && len (sha ) > 0 {
92+ opts .Ref = sha [0 ]
93+ }
6294
63- // Use the GitHub client to fetch repository content
64- fileContent , directoryContent , _ , err := client .Repositories .GetContents (ctx , owner , repo , path , opts )
65- if err != nil {
66- return nil , err
67- }
95+ branch , ok := request .Params .Arguments ["branch" ].([]string )
96+ if ok && len (branch ) > 0 {
97+ opts .Ref = "refs/heads/" + branch [0 ]
98+ }
99+
100+ tag , ok := request .Params .Arguments ["tag" ].([]string )
101+ if ok && len (tag ) > 0 {
102+ opts .Ref = "refs/tags/" + tag [0 ]
103+ }
104+ prNumber , ok := request .Params .Arguments ["pr_number" ].([]string )
105+ if ok && len (prNumber ) > 0 {
106+ opts .Ref = "refs/pull/" + prNumber [0 ] + "/head"
107+ }
68108
69- if directoryContent != nil {
70- // Process the directory content and return it as resource contents
71- var resources []mcp.ResourceContents
72- for _ , entry := range directoryContent {
73- mimeType := "text/directory"
74- if entry .GetType () == "file" {
75- mimeType = mime .TypeByExtension (filepath .Ext (entry .GetName ()))
109+ fileContent , directoryContent , _ , err := client .Repositories .GetContents (ctx , owner , repo , path , opts )
110+ if err != nil {
111+ return nil , err
112+ }
113+
114+ if directoryContent != nil {
115+ var resources []mcp.ResourceContents
116+ for _ , entry := range directoryContent {
117+ mimeType := "text/directory"
118+ if entry .GetType () == "file" {
119+ // this is system dependent, and a best guess
120+ ext := filepath .Ext (entry .GetName ())
121+ mimeType = mime .TypeByExtension (ext )
122+ if ext == ".md" {
123+ mimeType = "text/markdown"
76124 }
77- resources = append (resources , mcp.TextResourceContents {
78- URI : entry .GetHTMLURL (),
79- MIMEType : mimeType ,
80- Text : entry .GetName (),
81- })
125+ }
126+ resources = append (resources , mcp.TextResourceContents {
127+ URI : entry .GetHTMLURL (),
128+ MIMEType : mimeType ,
129+ Text : entry .GetName (),
130+ })
131+
132+ }
133+ return resources , nil
82134
135+ }
136+ if fileContent != nil {
137+ if fileContent .Content != nil {
138+ // download the file content from fileContent.GetDownloadURL() and use the content-type header to determine the MIME type
139+ // and return the content as a blob unless it is a text file, where you can return the content as text
140+ req , err := http .NewRequest ("GET" , fileContent .GetDownloadURL (), nil )
141+ if err != nil {
142+ return nil , fmt .Errorf ("failed to create request: %w" , err )
83143 }
84- return resources , nil
85144
86- } else if fileContent != nil {
87- // Process the file content and return it as a binary resource
145+ resp , err := client .Client ().Do (req )
146+ if err != nil {
147+ return nil , fmt .Errorf ("failed to send request: %w" , err )
148+ }
149+ defer func () { _ = resp .Body .Close () }()
88150
89- if fileContent . Content != nil {
90- decodedContent , err := fileContent . GetContent ( )
151+ if resp . StatusCode != http . StatusOK {
152+ body , err := io . ReadAll ( resp . Body )
91153 if err != nil {
92- return nil , err
154+ return nil , fmt . Errorf ( "failed to read response body: %w" , err )
93155 }
156+ return nil , fmt .Errorf ("failed to fetch file content: %s" , string (body ))
157+ }
94158
95- mimeType := mime .TypeByExtension (filepath .Ext (fileContent .GetName ()))
96-
97- // Check if the file is text-based
98- if strings .HasPrefix (mimeType , "text" ) {
99- // Return as TextResourceContents
100- return []mcp.ResourceContents {
101- mcp.TextResourceContents {
102- URI : request .Params .URI ,
103- MIMEType : mimeType ,
104- Text : decodedContent ,
105- },
106- }, nil
159+ ext := filepath .Ext (fileContent .GetName ())
160+ mimeType := resp .Header .Get ("Content-Type" )
161+ if ext == ".md" {
162+ mimeType = "text/markdown"
163+ } else if mimeType == "" {
164+ // backstop to the file extension if the content type is not set
165+ mimeType = mime .TypeByExtension (filepath .Ext (fileContent .GetName ()))
166+ }
167+
168+ // if the content is a string, return it as text
169+ if strings .HasPrefix (mimeType , "text" ) {
170+ content , err := io .ReadAll (resp .Body )
171+ if err != nil {
172+ return nil , fmt .Errorf ("failed to parse the response body: %w" , err )
107173 }
108174
109- // Otherwise, return as BlobResourceContents
110175 return []mcp.ResourceContents {
111- mcp.BlobResourceContents {
176+ mcp.TextResourceContents {
112177 URI : request .Params .URI ,
113178 MIMEType : mimeType ,
114- Blob : base64 . StdEncoding . EncodeToString ([] byte ( decodedContent )), // Encode content as Base64
179+ Text : string ( content ),
115180 },
116181 }, nil
117182 }
118- }
183+ // otherwise, read the content and encode it as base64
184+ decodedContent , err := io .ReadAll (resp .Body )
185+ if err != nil {
186+ return nil , fmt .Errorf ("failed to parse the response body: %w" , err )
187+ }
119188
120- return nil , nil
189+ return []mcp.ResourceContents {
190+ mcp.BlobResourceContents {
191+ URI : request .Params .URI ,
192+ MIMEType : mimeType ,
193+ Blob : base64 .StdEncoding .EncodeToString (decodedContent ), // Encode content as Base64
194+ },
195+ }, nil
196+ }
121197 }
198+
199+ return nil , errors .New ("no repository resource content found" )
200+ }
122201}
0 commit comments