Skip to content

Commit ede9f22

Browse files
chore: use raw repo resources (#70)
* use raw repo URIs for resources * fetch repository content from raw urls * ensure no error in test write * Update pkg/github/repository_resource.go Co-authored-by: Copilot <[email protected]> * use appropriate file name for text file test --------- Co-authored-by: Copilot <[email protected]>
1 parent ad58220 commit ede9f22

File tree

2 files changed

+122
-24
lines changed

2 files changed

+122
-24
lines changed

pkg/github/repository_resource.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import (
44
"context"
55
"encoding/base64"
66
"errors"
7+
"fmt"
8+
"io"
79
"mime"
10+
"net/http"
811
"path/filepath"
912
"strings"
1013

@@ -113,7 +116,12 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C
113116
for _, entry := range directoryContent {
114117
mimeType := "text/directory"
115118
if entry.GetType() == "file" {
116-
mimeType = mime.TypeByExtension(filepath.Ext(entry.GetName()))
119+
// this is system dependent, and a best guess
120+
ext := filepath.Ext(entry.GetName())
121+
mimeType = mime.TypeByExtension(ext)
122+
if ext == ".md" {
123+
mimeType = "text/markdown"
124+
}
117125
}
118126
resources = append(resources, mcp.TextResourceContents{
119127
URI: entry.GetHTMLURL(),
@@ -127,28 +135,62 @@ func repositoryResourceContentsHandler(client *github.Client) func(ctx context.C
127135
}
128136
if fileContent != nil {
129137
if fileContent.Content != nil {
130-
decodedContent, err := fileContent.GetContent()
138+
// download the file content from fileContent.GetDownloadURL() and use the content-type header to determine the MIME type
139+
// and return the content as a blob unless it is a text file, where you can return the content as text
140+
req, err := http.NewRequest("GET", fileContent.GetDownloadURL(), nil)
131141
if err != nil {
132-
return nil, err
142+
return nil, fmt.Errorf("failed to create request: %w", err)
133143
}
134144

135-
mimeType := mime.TypeByExtension(filepath.Ext(fileContent.GetName()))
145+
resp, err := client.Client().Do(req)
146+
if err != nil {
147+
return nil, fmt.Errorf("failed to send request: %w", err)
148+
}
149+
defer func() { _ = resp.Body.Close() }()
150+
151+
if resp.StatusCode != http.StatusOK {
152+
body, err := io.ReadAll(resp.Body)
153+
if err != nil {
154+
return nil, fmt.Errorf("failed to read response body: %w", err)
155+
}
156+
return nil, fmt.Errorf("failed to fetch file content: %s", string(body))
157+
}
158+
159+
ext := filepath.Ext(fileContent.GetName())
160+
mimeType := resp.Header.Get("Content-Type")
161+
if ext == ".md" {
162+
mimeType = "text/markdown"
163+
} else if mimeType == "" {
164+
// backstop to the file extension if the content type is not set
165+
mimeType = mime.TypeByExtension(filepath.Ext(fileContent.GetName()))
166+
}
136167

168+
// if the content is a string, return it as text
137169
if strings.HasPrefix(mimeType, "text") {
170+
content, err := io.ReadAll(resp.Body)
171+
if err != nil {
172+
return nil, fmt.Errorf("failed to parse the response body: %w", err)
173+
}
174+
138175
return []mcp.ResourceContents{
139176
mcp.TextResourceContents{
140177
URI: request.Params.URI,
141178
MIMEType: mimeType,
142-
Text: decodedContent,
179+
Text: string(content),
143180
},
144181
}, nil
145182
}
183+
// otherwise, read the content and encode it as base64
184+
decodedContent, err := io.ReadAll(resp.Body)
185+
if err != nil {
186+
return nil, fmt.Errorf("failed to parse the response body: %w", err)
187+
}
146188

147189
return []mcp.ResourceContents{
148190
mcp.BlobResourceContents{
149191
URI: request.Params.URI,
150192
MIMEType: mimeType,
151-
Blob: base64.StdEncoding.EncodeToString([]byte(decodedContent)), // Encode content as Base64
193+
Blob: base64.StdEncoding.EncodeToString(decodedContent), // Encode content as Base64
152194
},
153195
}, nil
154196
}

pkg/github/repository_resource_test.go

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package github
22

33
import (
44
"context"
5-
"encoding/base64"
65
"net/http"
76
"testing"
87

@@ -13,28 +12,35 @@ import (
1312
"github.com/stretchr/testify/require"
1413
)
1514

15+
var GetRawReposContentsByOwnerByRepoByPath mock.EndpointPattern = mock.EndpointPattern{
16+
Pattern: "/{owner}/{repo}/main/{path:.+}",
17+
Method: "GET",
18+
}
19+
1620
func Test_repositoryResourceContentsHandler(t *testing.T) {
1721
mockDirContent := []*github.RepositoryContent{
1822
{
19-
Type: github.Ptr("file"),
20-
Name: github.Ptr("README.md"),
21-
Path: github.Ptr("README.md"),
22-
SHA: github.Ptr("abc123"),
23-
Size: github.Ptr(42),
24-
HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
23+
Type: github.Ptr("file"),
24+
Name: github.Ptr("README.md"),
25+
Path: github.Ptr("README.md"),
26+
SHA: github.Ptr("abc123"),
27+
Size: github.Ptr(42),
28+
HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
29+
DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"),
2530
},
2631
{
27-
Type: github.Ptr("dir"),
28-
Name: github.Ptr("src"),
29-
Path: github.Ptr("src"),
30-
SHA: github.Ptr("def456"),
31-
HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"),
32+
Type: github.Ptr("dir"),
33+
Name: github.Ptr("src"),
34+
Path: github.Ptr("src"),
35+
SHA: github.Ptr("def456"),
36+
HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"),
37+
DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/src"),
3238
},
3339
}
3440
expectedDirContent := []mcp.TextResourceContents{
3541
{
3642
URI: "https://github.com/owner/repo/blob/main/README.md",
37-
MIMEType: "",
43+
MIMEType: "text/markdown",
3844
Text: "README.md",
3945
},
4046
{
@@ -44,20 +50,41 @@ func Test_repositoryResourceContentsHandler(t *testing.T) {
4450
},
4551
}
4652

47-
mockFileContent := &github.RepositoryContent{
53+
mockTextContent := &github.RepositoryContent{
4854
Type: github.Ptr("file"),
4955
Name: github.Ptr("README.md"),
5056
Path: github.Ptr("README.md"),
51-
Content: github.Ptr("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"), // Base64 encoded "# Test Repository\n\nThis is a test repository."
57+
Content: github.Ptr("# Test Repository\n\nThis is a test repository."),
5258
SHA: github.Ptr("abc123"),
5359
Size: github.Ptr(42),
5460
HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
5561
DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"),
5662
}
5763

64+
mockFileContent := &github.RepositoryContent{
65+
Type: github.Ptr("file"),
66+
Name: github.Ptr("data.png"),
67+
Path: github.Ptr("data.png"),
68+
Content: github.Ptr("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"), // Base64 encoded "# Test Repository\n\nThis is a test repository."
69+
SHA: github.Ptr("abc123"),
70+
Size: github.Ptr(42),
71+
HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/data.png"),
72+
DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/data.png"),
73+
}
74+
5875
expectedFileContent := []mcp.BlobResourceContents{
5976
{
60-
Blob: base64.StdEncoding.EncodeToString([]byte("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku")),
77+
Blob: "IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku",
78+
MIMEType: "image/png",
79+
URI: "",
80+
},
81+
}
82+
83+
expectedTextContent := []mcp.TextResourceContents{
84+
{
85+
Text: "# Test Repository\n\nThis is a test repository.",
86+
MIMEType: "text/markdown",
87+
URI: "",
6188
},
6289
}
6390

@@ -94,21 +121,50 @@ func Test_repositoryResourceContentsHandler(t *testing.T) {
94121
expectError: "repo is required",
95122
},
96123
{
97-
name: "successful file content fetch",
124+
name: "successful blob content fetch",
98125
mockedClient: mock.NewMockedHTTPClient(
99126
mock.WithRequestMatch(
100127
mock.GetReposContentsByOwnerByRepoByPath,
101128
mockFileContent,
102129
),
130+
mock.WithRequestMatchHandler(
131+
GetRawReposContentsByOwnerByRepoByPath,
132+
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
133+
w.Header().Set("Content-Type", "image/png")
134+
// as this is given as a png, it will return the content as a blob
135+
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
136+
require.NoError(t, err)
137+
}),
138+
),
103139
),
104140
requestArgs: map[string]any{
105141
"owner": []string{"owner"},
106142
"repo": []string{"repo"},
107-
"path": []string{"README.md"},
143+
"path": []string{"data.png"},
108144
"branch": []string{"main"},
109145
},
110146
expectedResult: expectedFileContent,
111147
},
148+
{
149+
name: "successful text content fetch",
150+
mockedClient: mock.NewMockedHTTPClient(
151+
mock.WithRequestMatch(
152+
mock.GetReposContentsByOwnerByRepoByPath,
153+
mockTextContent,
154+
),
155+
mock.WithRequestMatch(
156+
GetRawReposContentsByOwnerByRepoByPath,
157+
[]byte("# Test Repository\n\nThis is a test repository."),
158+
),
159+
),
160+
requestArgs: map[string]any{
161+
"owner": []string{"owner"},
162+
"repo": []string{"repo"},
163+
"path": []string{"README.md"},
164+
"branch": []string{"main"},
165+
},
166+
expectedResult: expectedTextContent,
167+
},
112168
{
113169
name: "successful directory content fetch",
114170
mockedClient: mock.NewMockedHTTPClient(

0 commit comments

Comments
 (0)