@@ -1,6 +1,7 @@ package github import ( "bytes" "context" "encoding/base64" "errors" Expand All @@ -15,107 +16,120 @@ import ( "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/yosida95/uritemplate/v3" ) var ( repositoryResourceContentURITemplate = uritemplate.MustNew("repo://{owner}/{repo}/contents{/path*}") repositoryResourceBranchContentURITemplate = uritemplate.MustNew("repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}") repositoryResourceCommitContentURITemplate = uritemplate.MustNew("repo://{owner}/{repo}/sha/{sha}/contents{/path*}") repositoryResourceTagContentURITemplate = uritemplate.MustNew("repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}") repositoryResourcePrContentURITemplate = uritemplate.MustNew("repo://{owner}/{repo}/refs/pull/{prNumber}/head/contents{/path*}") ) // GetRepositoryResourceContent defines the resource template and handler for getting repository content. func GetRepositoryResourceContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/contents{/path*}", // Resource template t("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION", "Repository Content"), ), RepositoryResourceContentsHandler(getClient, getRawClient) func GetRepositoryResourceContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) { return mcp.ResourceTemplate{ Name: "repository_content", URITemplate: repositoryResourceContentURITemplate.Raw(), // Resource template Description: t("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION", "Repository Content"), }, RepositoryResourceContentsHandler(getClient, getRawClient, repositoryResourceContentURITemplate) } // GetRepositoryResourceBranchContent defines the resource template and handler for getting repository content for a branch. func GetRepositoryResourceBranchContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}", // Resource template t("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION", "Repository Content for specific branch"), ), RepositoryResourceContentsHandler(getClient, getRawClient) func GetRepositoryResourceBranchContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) { return mcp.ResourceTemplate{ Name: "repository_content_branch", URITemplate: repositoryResourceBranchContentURITemplate.Raw(), // Resource template Description: t("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION", "Repository Content for specific branch"), }, RepositoryResourceContentsHandler(getClient, getRawClient, repositoryResourceBranchContentURITemplate) } // GetRepositoryResourceCommitContent defines the resource template and handler for getting repository content for a commit. func GetRepositoryResourceCommitContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/sha/{sha}/contents{/path*}", // Resource template t("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION", "Repository Content for specific commit"), ), RepositoryResourceContentsHandler(getClient, getRawClient) func GetRepositoryResourceCommitContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) { return mcp.ResourceTemplate{ Name: "repository_content_commit", URITemplate: repositoryResourceCommitContentURITemplate.Raw(), // Resource template Description: t("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION", "Repository Content for specific commit"), }, RepositoryResourceContentsHandler(getClient, getRawClient, repositoryResourceCommitContentURITemplate) } // GetRepositoryResourceTagContent defines the resource template and handler for getting repository content for a tag. func GetRepositoryResourceTagContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}", // Resource template t("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION", "Repository Content for specific tag"), ), RepositoryResourceContentsHandler(getClient, getRawClient) func GetRepositoryResourceTagContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) { return mcp.ResourceTemplate{ Name: "repository_content_tag", URITemplate: repositoryResourceTagContentURITemplate.Raw(), // Resource template Description: t("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION", "Repository Content for specific tag"), }, RepositoryResourceContentsHandler(getClient, getRawClient, repositoryResourceTagContentURITemplate) } // GetRepositoryResourcePrContent defines the resource template and handler for getting repository content for a pull request. func GetRepositoryResourcePrContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/pull/{prNumber}/head/contents{/path*}", // Resource template t("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION", "Repository Content for specific pull request"), ), RepositoryResourceContentsHandler(getClient, getRawClient) func GetRepositoryResourcePrContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) { return mcp.ResourceTemplate{ Name: "repository_content_pr", URITemplate: repositoryResourcePrContentURITemplate.Raw(), // Resource template Description: t("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION", "Repository Content for specific pull request"), }, RepositoryResourceContentsHandler(getClient, getRawClient, repositoryResourcePrContentURITemplate) } // RepositoryResourceContentsHandler returns a handler function for repository content requests. func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.GetRawClientFn) func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { return func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { // the matcher will give []string with one element // https://github.com/mark3labs/mcp-go/pull/54 o, ok := request.Params.Arguments["owner"].([]string) if !ok || len(o) == 0 { func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.GetRawClientFn, resourceURITemplate *uritemplate.Template) mcp.ResourceHandler { return func(ctx context.Context, request *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { // Match the URI to extract parameters uriValues := resourceURITemplate.Match(request.Params.URI) if uriValues == nil { return nil, fmt.Errorf("failed to match URI: %s", request.Params.URI) } // Extract required vars owner := uriValues.Get("owner").String() repo := uriValues.Get("repo").String() if owner == "" { return nil, errors.New("owner is required") } owner := o[0] r, ok := request.Params.Arguments["repo"].([]string) if !ok || len(r) == 0 { if repo == "" { return nil, errors.New("repo is required") } repo := r[0] // path should be a joined list of the path parts path := "" p, ok := request.Params.Arguments["path"].([]string) if ok { path = strings.Join(p, "/") } path := uriValues.Get("path").String() opts := &github.RepositoryContentGetOptions{} rawOpts := &raw.ContentOpts{} sha, ok :=request.Params.Arguments[ "sha"].([]string ) ifok && len( sha) > 0 { opts.Ref = sha[0] rawOpts.SHA = sha[0] sha :=uriValues.Get( "sha").String( ) if sha != "" { opts.Ref = sha rawOpts.SHA = sha } branch, ok :=request.Params.Arguments[ "branch"].([]string ) ifok && len( branch) > 0 { opts.Ref = "refs/heads/" + branch[0] rawOpts.Ref = "refs/heads/" + branch[0] branch :=uriValues.Get( "branch").String( ) if branch != "" { opts.Ref = "refs/heads/" + branch rawOpts.Ref = "refs/heads/" + branch } tag, ok :=request.Params.Arguments[ "tag"].([]string ) ifok && len( tag) > 0 { opts.Ref = "refs/tags/" + tag[0] rawOpts.Ref = "refs/tags/" + tag[0] tag :=uriValues.Get( "tag").String( ) if tag != "" { opts.Ref = "refs/tags/" + tag rawOpts.Ref = "refs/tags/" + tag } prNumber, ok := request.Params.Arguments["prNumber"].([]string) if ok && len(prNumber) > 0 { prNumber := uriValues.Get("prNumber").String() if prNumber != "" { // fetch the PR from the API to get the latest commit and use SHA githubClient, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) } prNum, err := strconv.Atoi(prNumber[0] ) prNum, err := strconv.Atoi(prNumber) if err != nil { return nil, fmt.Errorf("invalid pull request number: %w", err) } Expand Down Expand Up @@ -161,19 +175,33 @@ func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.G switch { case strings.HasPrefix(mimeType, "text"), strings.HasPrefix(mimeType, "application"): return []mcp.ResourceContents{ mcp.TextResourceContents{ URI: request.Params.URI, MIMEType: mimeType, Text: string(content), return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{ { URI: request.Params.URI, MIMEType: mimeType, Text: string(content), }, }, }, nil default: return []mcp.ResourceContents{ mcp.BlobResourceContents{ URI: request.Params.URI, MIMEType: mimeType, Blob: base64.StdEncoding.EncodeToString(content), var buf bytes.Buffer base64Encoder := base64.NewEncoder(base64.StdEncoding, &buf) _, err := base64Encoder.Write(content) if err != nil { return nil, fmt.Errorf("failed to base64 encode content: %w", err) } if err := base64Encoder.Close(); err != nil { return nil, fmt.Errorf("failed to close base64 encoder: %w", err) } return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{ { URI: request.Params.URI, MIMEType: mimeType, Blob: buf.Bytes(), }, }, }, nil } Expand Down