@@ -3,7 +3,11 @@ package github
3
3
import (
4
4
"context"
5
5
"encoding/base64"
6
+ "errors"
7
+ "fmt"
8
+ "io"
6
9
"mime"
10
+ "net/http"
7
11
"path/filepath"
8
12
"strings"
9
13
@@ -13,110 +17,185 @@ import (
13
17
"github.com/mark3labs/mcp-go/server"
14
18
)
15
19
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 ) {
19
22
return mcp .NewResourceTemplate (
20
23
"repo://{owner}/{repo}/contents{/path*}" ,// Resource template
21
24
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 (
23
32
"repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}" ,// Resource template
24
33
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 (
26
41
"repo://{owner}/{repo}/sha/{sha}/contents{/path*}" ,// Resource template
27
42
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 (
29
50
"repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}" ,// Resource template
30
51
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 (
32
59
"repo://{owner}/{repo}/refs/pull/{pr_number}/head/contents{/path*}" ,// Resource template
33
60
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
+ }
36
64
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 ]
41
74
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 ]
43
80
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
+ }
48
87
49
- branch ,ok := request .Params .Arguments ["branch" ].([]string )
50
- if ok {
51
- opts .Ref = "refs/heads/" + branch [0 ]
52
- }
88
+ opts := & github.RepositoryContentGetOptions {}
53
89
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
+ }
62
94
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
+ }
68
108
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"
76
124
}
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
82
134
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 )
83
143
}
84
- return resources ,nil
85
144
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 () }()
88
150
89
- if fileContent . Content != nil {
90
- decodedContent ,err := fileContent . GetContent ( )
151
+ if resp . StatusCode != http . StatusOK {
152
+ body ,err := io . ReadAll ( resp . Body )
91
153
if err != nil {
92
- return nil ,err
154
+ return nil ,fmt . Errorf ( "failed to read response body: %w" , err )
93
155
}
156
+ return nil ,fmt .Errorf ("failed to fetch file content: %s" ,string (body ))
157
+ }
94
158
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 )
107
173
}
108
174
109
- // Otherwise, return as BlobResourceContents
110
175
return []mcp.ResourceContents {
111
- mcp.BlobResourceContents {
176
+ mcp.TextResourceContents {
112
177
URI :request .Params .URI ,
113
178
MIMEType :mimeType ,
114
- Blob :base64 . StdEncoding . EncodeToString ([] byte ( decodedContent )), // Encode content as Base64
179
+ Text :string ( content ),
115
180
},
116
181
},nil
117
182
}
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
+ }
119
188
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
+ }
121
197
}
198
+
199
+ return nil ,errors .New ("no repository resource content found" )
200
+ }
122
201
}