Spaces:
Build error
Build error
| package github | |
| import ( | |
| "context" | |
| "encoding/base64" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "mime" | |
| "net/http" | |
| "path/filepath" | |
| "strconv" | |
| "strings" | |
| "github.com/github/github-mcp-server/pkg/raw" | |
| "github.com/github/github-mcp-server/pkg/translations" | |
| "github.com/google/go-github/v74/github" | |
| "github.com/mark3labs/mcp-go/mcp" | |
| "github.com/mark3labs/mcp-go/server" | |
| ) | |
| // 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) | |
| } | |
| // 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) | |
| } | |
| // 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) | |
| } | |
| // 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) | |
| } | |
| // 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) | |
| } | |
| // 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 { | |
| return nil, errors.New("owner is required") | |
| } | |
| owner := o[0] | |
| r, ok := request.Params.Arguments["repo"].([]string) | |
| if !ok || len(r) == 0 { | |
| 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, "/") | |
| } | |
| opts := &github.RepositoryContentGetOptions{} | |
| rawOpts := &raw.ContentOpts{} | |
| sha, ok := request.Params.Arguments["sha"].([]string) | |
| if ok && len(sha) > 0 { | |
| opts.Ref = sha[0] | |
| rawOpts.SHA = sha[0] | |
| } | |
| branch, ok := request.Params.Arguments["branch"].([]string) | |
| if ok && len(branch) > 0 { | |
| opts.Ref = "refs/heads/" + branch[0] | |
| rawOpts.Ref = "refs/heads/" + branch[0] | |
| } | |
| tag, ok := request.Params.Arguments["tag"].([]string) | |
| if ok && len(tag) > 0 { | |
| opts.Ref = "refs/tags/" + tag[0] | |
| rawOpts.Ref = "refs/tags/" + tag[0] | |
| } | |
| prNumber, ok := request.Params.Arguments["prNumber"].([]string) | |
| if ok && len(prNumber) > 0 { | |
| // 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]) | |
| if err != nil { | |
| return nil, fmt.Errorf("invalid pull request number: %w", err) | |
| } | |
| pr, _, err := githubClient.PullRequests.Get(ctx, owner, repo, prNum) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to get pull request: %w", err) | |
| } | |
| sha := pr.GetHead().GetSHA() | |
| rawOpts.SHA = sha | |
| opts.Ref = sha | |
| } | |
| // if it's a directory | |
| if path == "" || strings.HasSuffix(path, "/") { | |
| return nil, fmt.Errorf("directories are not supported: %s", path) | |
| } | |
| rawClient, err := getRawClient(ctx) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to get GitHub raw content client: %w", err) | |
| } | |
| resp, err := rawClient.GetRawContent(ctx, owner, repo, path, rawOpts) | |
| defer func() { | |
| _ = resp.Body.Close() | |
| }() | |
| // If the raw content is not found, we will fall back to the GitHub API (in case it is a directory) | |
| switch { | |
| case err != nil: | |
| return nil, fmt.Errorf("failed to get raw content: %w", err) | |
| case resp.StatusCode == http.StatusOK: | |
| ext := filepath.Ext(path) | |
| mimeType := resp.Header.Get("Content-Type") | |
| if ext == ".md" { | |
| mimeType = "text/markdown" | |
| } else if mimeType == "" { | |
| mimeType = mime.TypeByExtension(ext) | |
| } | |
| content, err := io.ReadAll(resp.Body) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read file content: %w", err) | |
| } | |
| switch { | |
| case strings.HasPrefix(mimeType, "text"), strings.HasPrefix(mimeType, "application"): | |
| return []mcp.ResourceContents{ | |
| mcp.TextResourceContents{ | |
| 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), | |
| }, | |
| }, nil | |
| } | |
| case resp.StatusCode != http.StatusNotFound: | |
| // If we got a response but it is not 200 OK, we return an error | |
| body, err := io.ReadAll(resp.Body) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response body: %w", err) | |
| } | |
| return nil, fmt.Errorf("failed to fetch raw content: %s", string(body)) | |
| default: | |
| // This should be unreachable because GetContents should return an error if neither file nor directory content is found. | |
| return nil, errors.New("404 Not Found") | |
| } | |
| } | |
| } | |