Content-Length: 28456 | pFad | http://github.com/github/github-mcp-server/pull/650.patch
thub.com
From 3269af42718e7c5c5d9ec7af679223f34d350576 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Mon, 7 Jul 2025 16:56:24 +0100
Subject: [PATCH 1/9] add contingency to match path in git tree
---
pkg/github/repositories.go | 44 ++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index cf71a5839..2100c7f78 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -610,6 +610,31 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
}
return mcp.NewToolResultText(string(r)), nil
}
+
+ // The path does not point to a file or directory.
+ // Instead let's try to find it in the Git Tree by matching the end of the path.
+
+ // Step 1: Get Git Tree recursively
+ tree, resp, err := client.Git.GetTree(ctx, owner, repo, ref, true)
+ if err != nil {
+ return ghErrors.NewGitHubAPIErrorResponse(ctx,
+ "failed to get git tree",
+ resp,
+ err,
+ ), nil
+ }
+
+ // Step 2: Filter tree for matching paths
+ const maxMatchingFiles = 3
+ matchingFiles := filterPaths(tree.Entries, path, maxMatchingFiles)
+ if len(matchingFiles) > 0 {
+ matchingFilesJSON, err := json.Marshal(matchingFiles)
+ if err != nil {
+ return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil
+ }
+ return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but possible matches are: %s", matchingFilesJSON)), nil
+ }
+
return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil
}
}
@@ -1293,3 +1318,22 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m
return mcp.NewToolResultText(string(r)), nil
}
}
+
+// filterPaths filters the entries in a GitHub tree to find paths that
+// match the given suffix.
+func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string {
+ matchedPaths := []string{}
+ for _, entry := range entries {
+ if len(matchedPaths) == maxResults {
+ break // Limit the number of results to maxResults
+ }
+ entryPath := entry.GetPath()
+ if entryPath == "" {
+ continue // Skip empty paths
+ }
+ if strings.HasSuffix(entryPath, path) {
+ matchedPaths = append(matchedPaths, entryPath)
+ }
+ }
+ return matchedPaths
+}
From be2f36f79e0c135808368f40b05d2275f6560f7e Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 00:59:37 +0100
Subject: [PATCH 2/9] resolveGitReference helper
---
pkg/github/repositories.go | 77 ++++++++++++----------
pkg/github/repositories_test.go | 110 +++++++++++++++++++++++++++++++-
2 files changed, 153 insertions(+), 34 deletions(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index 2100c7f78..1dc63dc12 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -8,7 +8,6 @@ import (
"io"
"net/http"
"net/url"
- "strconv"
"strings"
ghErrors "github.com/github/github-mcp-server/pkg/errors"
@@ -495,33 +494,18 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
return mcp.NewToolResultError(err.Error()), nil
}
- rawOpts := &raw.ContentOpts{}
-
- if strings.HasPrefix(ref, "refs/pull/") {
- prNumber := strings.TrimSuffix(strings.TrimPrefix(ref, "refs/pull/"), "/head")
- if 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)
- 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()
- ref = ""
- }
+ client, err := getClient(ctx)
+ if err != nil {
+ return mcp.NewToolResultError("failed to get GitHub client"), nil
}
- rawOpts.SHA = sha
- rawOpts.Ref = ref
+ rawOpts, err := resolveGitReference(ctx, client, owner, repo, ref, sha)
+ if err != nil {
+ return mcp.NewToolResultError(err.Error()), nil
+ }
- // If the path is (most likely) not to be a directory, we will first try to get the raw content from the GitHub raw content API.
+ // If the path is (most likely) not to be a directory, we will
+ // first try to get the raw content from the GitHub raw content API.
if path != "" && !strings.HasSuffix(path, "/") {
rawClient, err := getRawClient(ctx)
@@ -580,13 +564,8 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
}
}
- client, err := getClient(ctx)
- if err != nil {
- return mcp.NewToolResultError("failed to get GitHub client"), nil
- }
-
- if sha != "" {
- ref = sha
+ if rawOpts.SHA != "" {
+ ref = rawOpts.SHA
}
if strings.HasSuffix(path, "/") {
opts := &github.RepositoryContentGetOptions{Ref: ref}
@@ -632,7 +611,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil
}
- return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but possible matches are: %s", matchingFilesJSON)), nil
+ return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil
}
return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil
@@ -1337,3 +1316,35 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str
}
return matchedPaths
}
+
+// resolveGitReference resolves git references with the following logic:
+// 1. If SHA is provided, it takes precedence
+// 2. If neither is provided, use the default branch as ref
+// 3. Get SHA from the ref
+// Refs can look like `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`
+// The function returns the resolved ref, SHA and any error.
+func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, error) {
+ // 1. If SHA is provided, use it directly
+ if sha != "" {
+ return &raw.ContentOpts{Ref: "", SHA: sha}, nil
+ }
+
+ // 2. If neither provided, use the default branch as ref
+ if ref == "" {
+ repoInfo, _, err := githubClient.Repositories.Get(ctx, owner, repo)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get repository info: %w", err)
+ }
+ ref = fmt.Sprintf("refs/heads/%s", repoInfo.GetDefaultBranch())
+ }
+
+ // 3. Get the SHA from the ref
+ reference, _, err := githubClient.Git.GetRef(ctx, owner, repo, ref)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get reference for default branch: %w", err)
+ }
+ sha = reference.GetObject().GetSHA()
+
+ // Use provided ref, or it will be empty which defaults to the default branch
+ return &raw.ContentOpts{Ref: ref, SHA: sha}, nil
+}
diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go
index b621cec43..c81796899 100644
--- a/pkg/github/repositories_test.go
+++ b/pkg/github/repositories_test.go
@@ -69,6 +69,13 @@ func Test_GetFileContents(t *testing.T) {
{
name: "successful text content fetch",
mockedClient: mock.NewMockedHTTPClient(
+ mock.WithRequestMatchHandler(
+ mock.GetReposGitRefByOwnerByRepoByRef,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
+ }),
+ ),
mock.WithRequestMatchHandler(
raw.GetRawReposContentsByOwnerByRepoByBranchByPath,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -93,6 +100,13 @@ func Test_GetFileContents(t *testing.T) {
{
name: "successful file blob content fetch",
mockedClient: mock.NewMockedHTTPClient(
+ mock.WithRequestMatchHandler(
+ mock.GetReposGitRefByOwnerByRepoByRef,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
+ }),
+ ),
mock.WithRequestMatchHandler(
raw.GetRawReposContentsByOwnerByRepoByBranchByPath,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -117,6 +131,20 @@ func Test_GetFileContents(t *testing.T) {
{
name: "successful directory content fetch",
mockedClient: mock.NewMockedHTTPClient(
+ mock.WithRequestMatchHandler(
+ mock.GetReposByOwnerByRepo,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`))
+ }),
+ ),
+ mock.WithRequestMatchHandler(
+ mock.GetReposGitRefByOwnerByRepoByRef,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
+ }),
+ ),
mock.WithRequestMatchHandler(
mock.GetReposContentsByOwnerByRepoByPath,
expectQueryParams(t, map[string]string{}).andThen(
@@ -143,6 +171,13 @@ func Test_GetFileContents(t *testing.T) {
{
name: "content fetch fails",
mockedClient: mock.NewMockedHTTPClient(
+ mock.WithRequestMatchHandler(
+ mock.GetReposGitRefByOwnerByRepoByRef,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
+ }),
+ ),
mock.WithRequestMatchHandler(
mock.GetReposContentsByOwnerByRepoByPath,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -203,7 +238,7 @@ func Test_GetFileContents(t *testing.T) {
textContent := getTextResult(t, result)
var returnedContents []*github.RepositoryContent
err = json.Unmarshal([]byte(textContent.Text), &returnedContents)
- require.NoError(t, err)
+ require.NoError(t, err, "Failed to unmarshal directory content result: %v", textContent.Text)
assert.Len(t, returnedContents, len(expected))
for i, content := range returnedContents {
assert.Equal(t, *expected[i].Name, *content.Name)
@@ -2049,3 +2084,76 @@ func Test_GetTag(t *testing.T) {
})
}
}
+
+func Test_ResolveGitReference(t *testing.T) {
+
+ ctx := context.Background()
+ owner := "owner"
+ repo := "repo"
+ mockedClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatchHandler(
+ mock.GetReposByOwnerByRepo,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`))
+ }),
+ ),
+ mock.WithRequestMatchHandler(
+ mock.GetReposGitRefByOwnerByRepoByRef,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "123sha456"}}`))
+ }),
+ ),
+ )
+
+ tests := []struct {
+ name string
+ ref string
+ sha string
+ expectedOutput *raw.ContentOpts
+ }{
+ {
+ name: "sha takes precedence over ref",
+ ref: "refs/heads/main",
+ sha: "123sha456",
+ expectedOutput: &raw.ContentOpts{
+ SHA: "123sha456",
+ },
+ },
+ {
+ name: "use default branch if ref and sha both empty",
+ ref: "",
+ sha: "",
+ expectedOutput: &raw.ContentOpts{
+ Ref: "refs/heads/main",
+ SHA: "123sha456",
+ },
+ },
+ {
+ name: "get SHA from ref",
+ ref: "refs/heads/main",
+ sha: "",
+ expectedOutput: &raw.ContentOpts{
+ Ref: "refs/heads/main",
+ SHA: "123sha456",
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ // Setup client with mock
+ client := github.NewClient(mockedClient)
+ opts, err := resolveGitReference(ctx, client, owner, repo, tc.ref, tc.sha)
+ require.NoError(t, err)
+
+ if tc.expectedOutput.SHA != "" {
+ assert.Equal(t, tc.expectedOutput.SHA, opts.SHA)
+ }
+ if tc.expectedOutput.Ref != "" {
+ assert.Equal(t, tc.expectedOutput.Ref, opts.Ref)
+ }
+ })
+ }
+}
From 37a6088f0bd8702580726e70d5ea1b60c05a4e22 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 01:34:39 +0100
Subject: [PATCH 3/9] fix: handling of directories
---
pkg/github/repositories.go | 25 ++++++++++---------------
1 file changed, 10 insertions(+), 15 deletions(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index 1dc63dc12..44cbd2cf4 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -570,24 +570,14 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
if strings.HasSuffix(path, "/") {
opts := &github.RepositoryContentGetOptions{Ref: ref}
_, dirContent, resp, err := client.Repositories.GetContents(ctx, owner, repo, path, opts)
- if err != nil {
- return mcp.NewToolResultError("failed to get file contents"), nil
- }
- defer func() { _ = resp.Body.Close() }()
-
- if resp.StatusCode != 200 {
- body, err := io.ReadAll(resp.Body)
+ if err == nil && resp.StatusCode == http.StatusOK {
+ defer func() { _ = resp.Body.Close() }()
+ r, err := json.Marshal(dirContent)
if err != nil {
- return mcp.NewToolResultError("failed to read response body"), nil
+ return mcp.NewToolResultError("failed to marshal response"), nil
}
- return mcp.NewToolResultError(fmt.Sprintf("failed to get file contents: %s", string(body))), nil
+ return mcp.NewToolResultText(string(r)), nil
}
-
- r, err := json.Marshal(dirContent)
- if err != nil {
- return mcp.NewToolResultError("failed to marshal response"), nil
- }
- return mcp.NewToolResultText(string(r)), nil
}
// The path does not point to a file or directory.
@@ -1301,6 +1291,8 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m
// filterPaths filters the entries in a GitHub tree to find paths that
// match the given suffix.
func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string {
+ path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes
+
matchedPaths := []string{}
for _, entry := range entries {
if len(matchedPaths) == maxResults {
@@ -1311,6 +1303,9 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str
continue // Skip empty paths
}
if strings.HasSuffix(entryPath, path) {
+ if entry.GetType() == "tree" {
+ entryPath += "/" // show directories with a trailing slash
+ }
matchedPaths = append(matchedPaths, entryPath)
}
}
From 588866f031be4395b7404d9c734649e1ee9e3807 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 09:28:32 +0100
Subject: [PATCH 4/9] Test_filterPaths
---
pkg/github/repositories.go | 4 +++
pkg/github/repositories_test.go | 43 ++++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index 44cbd2cf4..869d3c162 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -1290,6 +1290,10 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m
// filterPaths filters the entries in a GitHub tree to find paths that
// match the given suffix.
+// maxResults limits the number of results returned to first maxResults entries,
+// a maxResults of -1 means no limit.
+// It returns a slice of strings containing the matching paths.
+// Directories are returned with a trailing slash.
func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string {
path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes
diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go
index c81796899..55c3aa088 100644
--- a/pkg/github/repositories_test.go
+++ b/pkg/github/repositories_test.go
@@ -2085,8 +2085,49 @@ func Test_GetTag(t *testing.T) {
}
}
-func Test_ResolveGitReference(t *testing.T) {
+func Test_filterPaths(t *testing.T) {
+ tests := []struct {
+ name string
+ tree []*github.TreeEntry
+ path string
+ maxResults int
+ expected []string
+ }{
+ {
+ name: "file name",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("folder/foo.txt"), Type: github.Ptr("blob")},
+ {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")},
+ {Path: github.Ptr("nested/folder/foo.txt"), Type: github.Ptr("blob")},
+ {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")},
+ },
+ path: "foo.txt",
+ maxResults: -1,
+ expected: []string{"folder/foo.txt", "nested/folder/foo.txt"},
+ },
+ {
+ name: "dir name",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")},
+ {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")},
+ },
+ path: "folder/",
+ maxResults: -1,
+ expected: []string{"folder/", "nested/folder/"},
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result := filterPaths(tc.tree, tc.path, tc.maxResults)
+ assert.Equal(t, tc.expected, result)
+ })
+ }
+}
+func Test_resolveGitReference(t *testing.T) {
ctx := context.Background()
owner := "owner"
repo := "repo"
From 1cc6f7c5b9f62aebb715c5426d72b5688e6ec1ef Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 09:36:32 +0100
Subject: [PATCH 5/9] filterPaths - trailing slashes
---
pkg/github/repositories.go | 13 +++++++++++--
pkg/github/repositories_test.go | 20 ++++++++++++++++++++
2 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index 869d3c162..ffcd1c7c7 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -1295,20 +1295,29 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m
// It returns a slice of strings containing the matching paths.
// Directories are returned with a trailing slash.
func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string {
- path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes
+ // Remove trailing slash for matching purposes, but flag whether we
+ // only want directories.
+ dirOnly := false
+ if strings.HasSuffix(path, "/") {
+ dirOnly = true
+ path = strings.TrimSuffix(path, "/")
+ }
matchedPaths := []string{}
for _, entry := range entries {
if len(matchedPaths) == maxResults {
break // Limit the number of results to maxResults
}
+ if dirOnly && entry.GetType() != "tree" {
+ continue // Skip non-directory entries if dirOnly is true
+ }
entryPath := entry.GetPath()
if entryPath == "" {
continue // Skip empty paths
}
if strings.HasSuffix(entryPath, path) {
if entry.GetType() == "tree" {
- entryPath += "/" // show directories with a trailing slash
+ entryPath += "/" // Return directories with a trailing slash
}
matchedPaths = append(matchedPaths, entryPath)
}
diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go
index 55c3aa088..81d80a2e5 100644
--- a/pkg/github/repositories_test.go
+++ b/pkg/github/repositories_test.go
@@ -2117,6 +2117,26 @@ func Test_filterPaths(t *testing.T) {
maxResults: -1,
expected: []string{"folder/", "nested/folder/"},
},
+ {
+ name: "dir and file match",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("name"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("name"), Type: github.Ptr("blob")},
+ },
+ path: "name", // No trailing slash can match both files and directories
+ maxResults: -1,
+ expected: []string{"name/", "name"},
+ },
+ {
+ name: "dir only match",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("name"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("name"), Type: github.Ptr("blob")},
+ },
+ path: "name/", // Trialing slash ensures only directories are matched
+ maxResults: -1,
+ expected: []string{"name/"},
+ },
}
for _, tc := range tests {
From 12c2cfe59ec8401759cc1d1e7b83a2279456c705 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 12:50:56 +0100
Subject: [PATCH 6/9] fix: close response body, improve error messages, docs
---
pkg/github/repositories.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index ffcd1c7c7..d33b51dae 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -501,7 +501,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
rawOpts, err := resolveGitReference(ctx, client, owner, repo, ref, sha)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return mcp.NewToolResultError(fmt.Sprintf("failed to resolve git reference: %s", err)), nil
}
// If the path is (most likely) not to be a directory, we will
@@ -592,6 +592,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
err,
), nil
}
+ defer func() { _ = resp.Body.Close() }()
// Step 2: Filter tree for matching paths
const maxMatchingFiles = 3
@@ -601,7 +602,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil
}
- return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil
+ return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil
}
return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil
@@ -1328,9 +1329,9 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str
// resolveGitReference resolves git references with the following logic:
// 1. If SHA is provided, it takes precedence
// 2. If neither is provided, use the default branch as ref
-// 3. Get SHA from the ref
+// 3. Get commit SHA from the ref
// Refs can look like `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`
-// The function returns the resolved ref, SHA and any error.
+// The function returns the resolved ref, commit SHA and any error.
func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, error) {
// 1. If SHA is provided, use it directly
if sha != "" {
From 8c40155f5ce07826587fc70f2101ea7e37d2ca76 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 13:07:16 +0100
Subject: [PATCH 7/9] update tool result message about resolved git ref
---
pkg/github/repositories.go | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index d33b51dae..f32d40fe5 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -602,7 +602,11 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil
}
- return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil
+ resolvedRefs, err := json.Marshal(rawOpts)
+ if err != nil {
+ return mcp.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil
+ }
+ return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved git ref to %s with possible path matches: %s", resolvedRefs, matchingFilesJSON)), nil
}
return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil
From bf673f8d07a108c4b3276b9a2beec1f93fe4d8ae Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Tue, 8 Jul 2025 13:37:58 +0100
Subject: [PATCH 8/9] unit test cases for filterPaths maxResults param
---
pkg/github/repositories_test.go | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go
index 81d80a2e5..0b9c5d9f9 100644
--- a/pkg/github/repositories_test.go
+++ b/pkg/github/repositories_test.go
@@ -2137,6 +2137,39 @@ func Test_filterPaths(t *testing.T) {
maxResults: -1,
expected: []string{"name/"},
},
+ {
+ name: "max results limit 2",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
+ },
+ path: "folder/",
+ maxResults: 2,
+ expected: []string{"folder/", "nested/folder/"},
+ },
+ {
+ name: "max results limit 1",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
+ },
+ path: "folder/",
+ maxResults: 1,
+ expected: []string{"folder/"},
+ },
+ {
+ name: "max results limit 0",
+ tree: []*github.TreeEntry{
+ {Path: github.Ptr("folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
+ {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
+ },
+ path: "folder/",
+ maxResults: 0,
+ expected: []string{},
+ },
}
for _, tc := range tests {
From 3aea320515696610aabdb207ce4c687023cf1a06 Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Fri, 11 Jul 2025 16:04:21 +0100
Subject: [PATCH 9/9] resolveGitReference - NewGitHubAPIErrorToCtx
---
pkg/github/repositories.go | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go
index b1fce6beb..732f20ab1 100644
--- a/pkg/github/repositories.go
+++ b/pkg/github/repositories.go
@@ -1344,17 +1344,19 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner
// 2. If neither provided, use the default branch as ref
if ref == "" {
- repoInfo, _, err := githubClient.Repositories.Get(ctx, owner, repo)
+ repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo)
if err != nil {
+ _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err)
return nil, fmt.Errorf("failed to get repository info: %w", err)
}
ref = fmt.Sprintf("refs/heads/%s", repoInfo.GetDefaultBranch())
}
// 3. Get the SHA from the ref
- reference, _, err := githubClient.Git.GetRef(ctx, owner, repo, ref)
+ reference, resp, err := githubClient.Git.GetRef(ctx, owner, repo, ref)
if err != nil {
- return nil, fmt.Errorf("failed to get reference for default branch: %w", err)
+ _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference", resp, err)
+ return nil, fmt.Errorf("failed to get reference: %w", err)
}
sha = reference.GetObject().GetSHA()
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/github/github-mcp-server/pull/650.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy