From afcb99c1f8f76bb337311253f6abec1e67571f19 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 2 Apr 2025 14:54:25 +0200 Subject: [PATCH 1/5] use raw repo URIs for resources --- pkg/github/repository_resource.go | 45 +++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index c909e9c3b..655656f4f 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -4,7 +4,10 @@ import ( "context" "encoding/base64" "errors" + "fmt" + "io" "mime" + "net/http" "path/filepath" "strings" @@ -113,6 +116,7 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C for _, entry := range directoryContent { mimeType := "text/directory" if entry.GetType() == "file" { + // this is system dependent, and a best guess mimeType = mime.TypeByExtension(filepath.Ext(entry.GetName())) } resources = append(resources, mcp.TextResourceContents{ @@ -127,28 +131,59 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C } if fileContent != nil { if fileContent.Content != nil { - decodedContent, err := fileContent.GetContent() + // download the file content from fileContent.GetDownloadURL() and use the content-type header to determine the MIME type + // and return the content as a blob unless it is a text file, where you can return the content as text + req, err := http.NewRequest("GET", fileContent.GetDownloadURL(), nil) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create request: %w", err) } - mimeType := mime.TypeByExtension(filepath.Ext(fileContent.GetName())) + resp, err := client.Client().Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + 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 get security analysis settings: %s", string(body)) + } + + mimeType := resp.Header.Get("Content-Type") + if mimeType == "" { + // backstop to the file extension if the content type is not set + mime.TypeByExtension(filepath.Ext(fileContent.GetName())) + } + // if the content is a string, return it as text if strings.HasPrefix(mimeType, "text") { + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to parse the response body: %w", err) + } + return []mcp.ResourceContents{ mcp.TextResourceContents{ URI: request.Params.URI, MIMEType: mimeType, - Text: decodedContent, + Text: string(content), }, }, nil } + // otherwise, read the content and encode it as base64 + decodedContent, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to parse the response body: %w", err) + } return []mcp.ResourceContents{ mcp.BlobResourceContents{ URI: request.Params.URI, MIMEType: mimeType, - Blob: base64.StdEncoding.EncodeToString([]byte(decodedContent)), // Encode content as Base64 + Blob: base64.StdEncoding.EncodeToString(decodedContent), // Encode content as Base64 }, }, nil } From f1d6aa53d0fae0f9c10c08d03ed693c3dfa46566 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 2 Apr 2025 23:10:19 +0200 Subject: [PATCH 2/5] fetch repository content from raw urls --- pkg/github/repository_resource.go | 13 +++- pkg/github/repository_resource_test.go | 91 +++++++++++++++++++++----- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index 655656f4f..cbf66c029 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -117,7 +117,11 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C mimeType := "text/directory" if entry.GetType() == "file" { // this is system dependent, and a best guess - mimeType = mime.TypeByExtension(filepath.Ext(entry.GetName())) + ext := filepath.Ext(entry.GetName()) + mimeType = mime.TypeByExtension(ext) + if ext == ".md" { + mimeType = "text/markdown" + } } resources = append(resources, mcp.TextResourceContents{ URI: entry.GetHTMLURL(), @@ -152,10 +156,13 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C return nil, fmt.Errorf("failed to get security analysis settings: %s", string(body)) } + ext := filepath.Ext(fileContent.GetName()) mimeType := resp.Header.Get("Content-Type") - if mimeType == "" { + if ext == ".md" { + mimeType = "text/markdown" + } else if mimeType == "" { // backstop to the file extension if the content type is not set - mime.TypeByExtension(filepath.Ext(fileContent.GetName())) + mimeType = mime.TypeByExtension(filepath.Ext(fileContent.GetName())) } // if the content is a string, return it as text diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index 702a488ca..05d35b7a4 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -2,7 +2,6 @@ package github import ( "context" - "encoding/base64" "net/http" "testing" @@ -13,28 +12,35 @@ import ( "github.com/stretchr/testify/require" ) +var GetRawReposContentsByOwnerByRepoByPath mock.EndpointPattern = mock.EndpointPattern{ + Pattern: "/{owner}/{repo}/main/{path:.+}", + Method: "GET", +} + func Test_repositoryResourceContentsHandler(t *testing.T) { mockDirContent := []*github.RepositoryContent{ { - Type: github.Ptr("file"), - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Size: github.Ptr(42), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), + Type: github.Ptr("file"), + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Size: github.Ptr(42), + HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), + DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"), }, { - Type: github.Ptr("dir"), - Name: github.Ptr("src"), - Path: github.Ptr("src"), - SHA: github.Ptr("def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"), + Type: github.Ptr("dir"), + Name: github.Ptr("src"), + Path: github.Ptr("src"), + SHA: github.Ptr("def456"), + HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"), + DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/src"), }, } expectedDirContent := []mcp.TextResourceContents{ { URI: "https://github.com/owner/repo/blob/main/README.md", - MIMEType: "", + MIMEType: "text/markdown", Text: "README.md", }, { @@ -44,20 +50,41 @@ func Test_repositoryResourceContentsHandler(t *testing.T) { }, } - mockFileContent := &github.RepositoryContent{ + mockTextContent := &github.RepositoryContent{ Type: github.Ptr("file"), Name: github.Ptr("README.md"), Path: github.Ptr("README.md"), - Content: github.Ptr("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"), // Base64 encoded "# Test Repository\n\nThis is a test repository." + Content: github.Ptr("# Test Repository\n\nThis is a test repository."), SHA: github.Ptr("abc123"), Size: github.Ptr(42), HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"), } + mockFileContent := &github.RepositoryContent{ + Type: github.Ptr("file"), + Name: github.Ptr("data.png"), + Path: github.Ptr("data.png"), + Content: github.Ptr("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"), // Base64 encoded "# Test Repository\n\nThis is a test repository." + SHA: github.Ptr("abc123"), + Size: github.Ptr(42), + HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/data.png"), + DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/data.png"), + } + expectedFileContent := []mcp.BlobResourceContents{ { - Blob: base64.StdEncoding.EncodeToString([]byte("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku")), + Blob: "IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku", + MIMEType: "image/png", + URI: "", + }, + } + + expectedTextContent := []mcp.TextResourceContents{ + { + Text: "# Test Repository\n\nThis is a test repository.", + MIMEType: "text/markdown", + URI: "", }, } @@ -94,21 +121,49 @@ func Test_repositoryResourceContentsHandler(t *testing.T) { expectError: "repo is required", }, { - name: "successful file content fetch", + name: "successful blob content fetch", mockedClient: mock.NewMockedHTTPClient( mock.WithRequestMatch( mock.GetReposContentsByOwnerByRepoByPath, mockFileContent, ), + mock.WithRequestMatchHandler( + GetRawReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + // as this is given as a png, it will return the content as a blob + w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + }), + ), ), requestArgs: map[string]any{ "owner": []string{"owner"}, "repo": []string{"repo"}, - "path": []string{"README.md"}, + "path": []string{"data.png"}, "branch": []string{"main"}, }, expectedResult: expectedFileContent, }, + { + name: "successful text content fetch", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.GetReposContentsByOwnerByRepoByPath, + mockTextContent, + ), + mock.WithRequestMatch( + GetRawReposContentsByOwnerByRepoByPath, + []byte("# Test Repository\n\nThis is a test repository."), + ), + ), + requestArgs: map[string]any{ + "owner": []string{"owner"}, + "repo": []string{"repo"}, + "path": []string{"data.png"}, + "branch": []string{"main"}, + }, + expectedResult: expectedTextContent, + }, { name: "successful directory content fetch", mockedClient: mock.NewMockedHTTPClient( From c699e24a8c3d172f9449a5a10038a546ada7ec5f Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 2 Apr 2025 23:17:27 +0200 Subject: [PATCH 3/5] ensure no error in test write --- pkg/github/repository_resource_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index 05d35b7a4..65d4d368c 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -132,7 +132,8 @@ func Test_repositoryResourceContentsHandler(t *testing.T) { http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "image/png") // as this is given as a png, it will return the content as a blob - w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) }), ), ), From 0213f400753185233653c01fd813c13c5997c095 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 2 Apr 2025 23:25:06 +0200 Subject: [PATCH 4/5] Update pkg/github/repository_resource.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/github/repository_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index cbf66c029..9fa74c3c6 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -153,7 +153,7 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } - return nil, fmt.Errorf("failed to get security analysis settings: %s", string(body)) + return nil, fmt.Errorf("failed to fetch file content: %s", string(body)) } ext := filepath.Ext(fileContent.GetName()) From 332b3602924658e3850fd7f9ad3ba66e9902811f Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 2 Apr 2025 23:27:33 +0200 Subject: [PATCH 5/5] use appropriate file name for text file test --- pkg/github/repository_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index 65d4d368c..0a5b0b0f0 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -160,7 +160,7 @@ func Test_repositoryResourceContentsHandler(t *testing.T) { requestArgs: map[string]any{ "owner": []string{"owner"}, "repo": []string{"repo"}, - "path": []string{"data.png"}, + "path": []string{"README.md"}, "branch": []string{"main"}, }, expectedResult: expectedTextContent, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy