11package github
22
33import (
4+ "bytes"
45 "context"
56 "encoding/base64"
67 "errors"
@@ -15,107 +16,120 @@ import (
1516 "github.com/github/github-mcp-server/pkg/raw"
1617 "github.com/github/github-mcp-server/pkg/translations"
1718 "github.com/google/go-github/v79/github"
18- "github.com/mark3labs/mcp-go/mcp"
19- "github.com/mark3labs/mcp-go/server"
19+ "github.com/modelcontextprotocol/go-sdk/mcp"
20+ "github.com/yosida95/uritemplate/v3"
21+ )
22+
23+ var (
24+ repositoryResourceContentURITemplate = uritemplate .MustNew ("repo://{owner}/{repo}/contents{/path*}" )
25+ repositoryResourceBranchContentURITemplate = uritemplate .MustNew ("repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}" )
26+ repositoryResourceCommitContentURITemplate = uritemplate .MustNew ("repo://{owner}/{repo}/sha/{sha}/contents{/path*}" )
27+ repositoryResourceTagContentURITemplate = uritemplate .MustNew ("repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}" )
28+ repositoryResourcePrContentURITemplate = uritemplate .MustNew ("repo://{owner}/{repo}/refs/pull/{prNumber}/head/contents{/path*}" )
2029)
2130
2231// GetRepositoryResourceContent defines the resource template and handler for getting repository content.
23- func GetRepositoryResourceContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
24- return mcp .NewResourceTemplate (
25- "repo://{owner}/{repo}/contents{/path*}" , // Resource template
26- t ("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION" , "Repository Content" ),
27- ),
28- RepositoryResourceContentsHandler (getClient , getRawClient )
32+ func GetRepositoryResourceContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , mcp.ResourceHandler ) {
33+ return mcp.ResourceTemplate {
34+ Name : "repository_content" ,
35+ URITemplate : repositoryResourceContentURITemplate .Raw (), // Resource template
36+ Description : t ("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION" , "Repository Content" ),
37+ },
38+ RepositoryResourceContentsHandler (getClient , getRawClient , repositoryResourceContentURITemplate )
2939}
3040
3141// GetRepositoryResourceBranchContent defines the resource template and handler for getting repository content for a branch.
32- func GetRepositoryResourceBranchContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
33- return mcp .NewResourceTemplate (
34- "repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}" , // Resource template
35- t ("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION" , "Repository Content for specific branch" ),
36- ),
37- RepositoryResourceContentsHandler (getClient , getRawClient )
42+ func GetRepositoryResourceBranchContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , mcp.ResourceHandler ) {
43+ return mcp.ResourceTemplate {
44+ Name : "repository_content_branch" ,
45+ URITemplate : repositoryResourceBranchContentURITemplate .Raw (), // Resource template
46+ Description : t ("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION" , "Repository Content for specific branch" ),
47+ },
48+ RepositoryResourceContentsHandler (getClient , getRawClient , repositoryResourceBranchContentURITemplate )
3849}
3950
4051// GetRepositoryResourceCommitContent defines the resource template and handler for getting repository content for a commit.
41- func GetRepositoryResourceCommitContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
42- return mcp .NewResourceTemplate (
43- "repo://{owner}/{repo}/sha/{sha}/contents{/path*}" , // Resource template
44- t ("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION" , "Repository Content for specific commit" ),
45- ),
46- RepositoryResourceContentsHandler (getClient , getRawClient )
52+ func GetRepositoryResourceCommitContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , mcp.ResourceHandler ) {
53+ return mcp.ResourceTemplate {
54+ Name : "repository_content_commit" ,
55+ URITemplate : repositoryResourceCommitContentURITemplate .Raw (), // Resource template
56+ Description : t ("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION" , "Repository Content for specific commit" ),
57+ },
58+ RepositoryResourceContentsHandler (getClient , getRawClient , repositoryResourceCommitContentURITemplate )
4759}
4860
4961// GetRepositoryResourceTagContent defines the resource template and handler for getting repository content for a tag.
50- func GetRepositoryResourceTagContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
51- return mcp .NewResourceTemplate (
52- "repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}" , // Resource template
53- t ("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION" , "Repository Content for specific tag" ),
54- ),
55- RepositoryResourceContentsHandler (getClient , getRawClient )
62+ func GetRepositoryResourceTagContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , mcp.ResourceHandler ) {
63+ return mcp.ResourceTemplate {
64+ Name : "repository_content_tag" ,
65+ URITemplate : repositoryResourceTagContentURITemplate .Raw (), // Resource template
66+ Description : t ("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION" , "Repository Content for specific tag" ),
67+ },
68+ RepositoryResourceContentsHandler (getClient , getRawClient , repositoryResourceTagContentURITemplate )
5669}
5770
5871// GetRepositoryResourcePrContent defines the resource template and handler for getting repository content for a pull request.
59- func GetRepositoryResourcePrContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , server.ResourceTemplateHandlerFunc ) {
60- return mcp .NewResourceTemplate (
61- "repo://{owner}/{repo}/refs/pull/{prNumber}/head/contents{/path*}" , // Resource template
62- t ("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION" , "Repository Content for specific pull request" ),
63- ),
64- RepositoryResourceContentsHandler (getClient , getRawClient )
72+ func GetRepositoryResourcePrContent (getClient GetClientFn , getRawClient raw.GetRawClientFn , t translations.TranslationHelperFunc ) (mcp.ResourceTemplate , mcp.ResourceHandler ) {
73+ return mcp.ResourceTemplate {
74+ Name : "repository_content_pr" ,
75+ URITemplate : repositoryResourcePrContentURITemplate .Raw (), // Resource template
76+ Description : t ("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION" , "Repository Content for specific pull request" ),
77+ },
78+ RepositoryResourceContentsHandler (getClient , getRawClient , repositoryResourcePrContentURITemplate )
6579}
6680
6781// RepositoryResourceContentsHandler returns a handler function for repository content requests.
68- func RepositoryResourceContentsHandler (getClient GetClientFn , getRawClient raw.GetRawClientFn ) func (ctx context.Context , request mcp.ReadResourceRequest ) ([]mcp.ResourceContents , error ) {
69- return func (ctx context.Context , request mcp.ReadResourceRequest ) ([]mcp.ResourceContents , error ) {
70- // the matcher will give []string with one element
71- // https://github.com/mark3labs/mcp-go/pull/54
72- o , ok := request .Params .Arguments ["owner" ].([]string )
73- if ! ok || len (o ) == 0 {
82+ func RepositoryResourceContentsHandler (getClient GetClientFn , getRawClient raw.GetRawClientFn , resourceURITemplate * uritemplate.Template ) mcp.ResourceHandler {
83+ return func (ctx context.Context , request * mcp.ReadResourceRequest ) (* mcp.ReadResourceResult , error ) {
84+ // Match the URI to extract parameters
85+ uriValues := resourceURITemplate .Match (request .Params .URI )
86+ if uriValues == nil {
87+ return nil , fmt .Errorf ("failed to match URI: %s" , request .Params .URI )
88+ }
89+
90+ // Extract required vars
91+ owner := uriValues .Get ("owner" ).String ()
92+ repo := uriValues .Get ("repo" ).String ()
93+
94+ if owner == "" {
7495 return nil , errors .New ("owner is required" )
7596 }
76- owner := o [0 ]
7797
78- r , ok := request .Params .Arguments ["repo" ].([]string )
79- if ! ok || len (r ) == 0 {
98+ if repo == "" {
8099 return nil , errors .New ("repo is required" )
81100 }
82- repo := r [0 ]
83101
84- // path should be a joined list of the path parts
85- path := ""
86- p , ok := request .Params .Arguments ["path" ].([]string )
87- if ok {
88- path = strings .Join (p , "/" )
89- }
102+ path := uriValues .Get ("path" ).String ()
90103
91104 opts := & github.RepositoryContentGetOptions {}
92105 rawOpts := & raw.ContentOpts {}
93106
94- sha , ok := request . Params . Arguments [ "sha" ].([] string )
95- if ok && len ( sha ) > 0 {
96- opts .Ref = sha [ 0 ]
97- rawOpts .SHA = sha [ 0 ]
107+ sha := uriValues . Get ( "sha" ). String ( )
108+ if sha != "" {
109+ opts .Ref = sha
110+ rawOpts .SHA = sha
98111 }
99112
100- branch , ok := request . Params . Arguments [ "branch" ].([] string )
101- if ok && len ( branch ) > 0 {
102- opts .Ref = "refs/heads/" + branch [ 0 ]
103- rawOpts .Ref = "refs/heads/" + branch [ 0 ]
113+ branch := uriValues . Get ( "branch" ). String ( )
114+ if branch != "" {
115+ opts .Ref = "refs/heads/" + branch
116+ rawOpts .Ref = "refs/heads/" + branch
104117 }
105118
106- tag , ok := request . Params . Arguments [ "tag" ].([] string )
107- if ok && len ( tag ) > 0 {
108- opts .Ref = "refs/tags/" + tag [ 0 ]
109- rawOpts .Ref = "refs/tags/" + tag [ 0 ]
119+ tag := uriValues . Get ( "tag" ). String ( )
120+ if tag != "" {
121+ opts .Ref = "refs/tags/" + tag
122+ rawOpts .Ref = "refs/tags/" + tag
110123 }
111- prNumber , ok := request .Params .Arguments ["prNumber" ].([]string )
112- if ok && len (prNumber ) > 0 {
124+
125+ prNumber := uriValues .Get ("prNumber" ).String ()
126+ if prNumber != "" {
113127 // fetch the PR from the API to get the latest commit and use SHA
114128 githubClient , err := getClient (ctx )
115129 if err != nil {
116130 return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
117131 }
118- prNum , err := strconv .Atoi (prNumber [ 0 ] )
132+ prNum , err := strconv .Atoi (prNumber )
119133 if err != nil {
120134 return nil , fmt .Errorf ("invalid pull request number: %w" , err )
121135 }
@@ -161,19 +175,33 @@ func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.G
161175
162176 switch {
163177 case strings .HasPrefix (mimeType , "text" ), strings .HasPrefix (mimeType , "application" ):
164- return []mcp.ResourceContents {
165- mcp.TextResourceContents {
166- URI : request .Params .URI ,
167- MIMEType : mimeType ,
168- Text : string (content ),
178+ return & mcp.ReadResourceResult {
179+ Contents : []* mcp.ResourceContents {
180+ {
181+ URI : request .Params .URI ,
182+ MIMEType : mimeType ,
183+ Text : string (content ),
184+ },
169185 },
170186 }, nil
171187 default :
172- return []mcp.ResourceContents {
173- mcp.BlobResourceContents {
174- URI : request .Params .URI ,
175- MIMEType : mimeType ,
176- Blob : base64 .StdEncoding .EncodeToString (content ),
188+ var buf bytes.Buffer
189+ base64Encoder := base64 .NewEncoder (base64 .StdEncoding , & buf )
190+ _ , err := base64Encoder .Write (content )
191+ if err != nil {
192+ return nil , fmt .Errorf ("failed to base64 encode content: %w" , err )
193+ }
194+ if err := base64Encoder .Close (); err != nil {
195+ return nil , fmt .Errorf ("failed to close base64 encoder: %w" , err )
196+ }
197+
198+ return & mcp.ReadResourceResult {
199+ Contents : []* mcp.ResourceContents {
200+ {
201+ URI : request .Params .URI ,
202+ MIMEType : mimeType ,
203+ Blob : buf .Bytes (),
204+ },
177205 },
178206 }, nil
179207 }
0 commit comments