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 == "" {
7495return 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 == "" {
8099return 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
91104opts := & github.RepositoryContentGetOptions {}
92105rawOpts := & 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
114128githubClient ,err := getClient (ctx )
115129if err != nil {
116130return nil ,fmt .Errorf ("failed to get GitHub client: %w" ,err )
117131}
118- prNum ,err := strconv .Atoi (prNumber [ 0 ] )
132+ prNum ,err := strconv .Atoi (prNumber )
119133if err != nil {
120134return nil ,fmt .Errorf ("invalid pull request number: %w" ,err )
121135}
@@ -161,19 +175,33 @@ func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.G
161175
162176switch {
163177case 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
171187default :
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}