Skip to content

Commit e26cf42

Browse files
Add Repository Tree Navigation Tool (#1164)
* add repo nav tool * comment responses * move into git.go * fix documentation * Update pkg/github/git.go Co-authored-by: Adam Holt <omgitsads@github.com> * Fix undefined variable error in GetRepositoryTree * Update git.go to use github.com/google/go-github/v77 for consistency with main branch --------- Co-authored-by: Adam Holt <omgitsads@github.com>
1 parent b68bec0 commit e26cf42

File tree

9 files changed

+400
-0
lines changed

9 files changed

+400
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ The following sets of tools are available:
400400
| `discussions` | GitHub Discussions related tools |
401401
| `experiments` | Experimental features that are not considered stable yet |
402402
| `gists` | GitHub Gist related tools |
403+
| `git` | GitHub Git API related tools for low-level Git operations |
403404
| `issues` | GitHub Issues related tools |
404405
| `labels` | GitHub Labels related tools |
405406
| `notifications` | GitHub Notifications related tools |
@@ -630,6 +631,19 @@ The following sets of tools are available:
630631

631632
<details>
632633

634+
<summary>Git</summary>
635+
636+
- **get_repository_tree** - Get repository tree
637+
- `owner`: Repository owner (username or organization) (string, required)
638+
- `path_filter`: Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory) (string, optional)
639+
- `recursive`: Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false (boolean, optional)
640+
- `repo`: Repository name (string, required)
641+
- `tree_sha`: The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch (string, optional)
642+
643+
</details>
644+
645+
<details>
646+
633647
<summary>Issues</summary>
634648

635649
- **add_issue_comment** - Add comment to issue

cmd/mcpcurl/mcpcurl

6.41 MB
Binary file not shown.

docs/remote-server.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
2626
| Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) |
2727
| Experiments | Experimental features that are not considered stable yet | https://api.githubcopilot.com/mcp/x/experiments | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/experiments/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%2Freadonly%22%7D) |
2828
| Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) |
29+
| Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) |
2930
| Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) |
3031
| Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) |
3132
| Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) |

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/github/github-mcp-server
33
go 1.24.0
44

55
require (
6+
github.com/google/go-github/v76 v76.0.0
67
github.com/google/go-github/v77 v77.0.0
78
github.com/josephburnett/jd v1.9.2
89
github.com/mark3labs/mcp-go v0.36.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2626
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
2727
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
2828
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
29+
github.com/google/go-github/v76 v76.0.0 h1:MCa9VQn+VG5GG7Y7BAkBvSRUN3o+QpaEOuZwFPJmdFA=
30+
github.com/google/go-github/v76 v76.0.0/go.mod h1:38+d/8pYDO4fBLYfBhXF5EKO0wA3UkXBjfmQapFsNCQ=
2931
github.com/google/go-github/v77 v77.0.0 h1:9DsKKbZqil5y/4Z9mNpZDQnpli6PJbqipSuuNdcbjwI=
3032
github.com/google/go-github/v77 v77.0.0/go.mod h1:c8VmGXRUmaZUqbctUcGEDWYnMrtzZfJhDSylEf1wfmA=
3133
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"annotations": {
3+
"title": "Get repository tree",
4+
"readOnlyHint": true
5+
},
6+
"description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description": "Repository owner (username or organization)",
11+
"type": "string"
12+
},
13+
"path_filter": {
14+
"description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
15+
"type": "string"
16+
},
17+
"recursive": {
18+
"default": false,
19+
"description": "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
20+
"type": "boolean"
21+
},
22+
"repo": {
23+
"description": "Repository name",
24+
"type": "string"
25+
},
26+
"tree_sha": {
27+
"description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
28+
"type": "string"
29+
}
30+
},
31+
"required": [
32+
"owner",
33+
"repo"
34+
],
35+
"type": "object"
36+
},
37+
"name": "get_repository_tree"
38+
}

pkg/github/git.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
10+
"github.com/github/github-mcp-server/pkg/translations"
11+
"github.com/google/go-github/v77/github"
12+
"github.com/mark3labs/mcp-go/mcp"
13+
"github.com/mark3labs/mcp-go/server"
14+
)
15+
16+
// TreeEntryResponse represents a single entry in a Git tree.
17+
type TreeEntryResponse struct {
18+
Path string `json:"path"`
19+
Type string `json:"type"`
20+
Size *int `json:"size,omitempty"`
21+
Mode string `json:"mode"`
22+
SHA string `json:"sha"`
23+
URL string `json:"url"`
24+
}
25+
26+
// TreeResponse represents the response structure for a Git tree.
27+
type TreeResponse struct {
28+
SHA string `json:"sha"`
29+
Truncated bool `json:"truncated"`
30+
Tree []TreeEntryResponse `json:"tree"`
31+
TreeSHA string `json:"tree_sha"`
32+
Owner string `json:"owner"`
33+
Repo string `json:"repo"`
34+
Recursive bool `json:"recursive"`
35+
Count int `json:"count"`
36+
}
37+
38+
// GetRepositoryTree creates a tool to get the tree structure of a GitHub repository.
39+
func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
40+
return mcp.NewTool("get_repository_tree",
41+
mcp.WithDescription(t("TOOL_GET_REPOSITORY_TREE_DESCRIPTION", "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA")),
42+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
43+
Title: t("TOOL_GET_REPOSITORY_TREE_USER_TITLE", "Get repository tree"),
44+
ReadOnlyHint: ToBoolPtr(true),
45+
}),
46+
mcp.WithString("owner",
47+
mcp.Required(),
48+
mcp.Description("Repository owner (username or organization)"),
49+
),
50+
mcp.WithString("repo",
51+
mcp.Required(),
52+
mcp.Description("Repository name"),
53+
),
54+
mcp.WithString("tree_sha",
55+
mcp.Description("The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch"),
56+
),
57+
mcp.WithBoolean("recursive",
58+
mcp.Description("Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false"),
59+
mcp.DefaultBool(false),
60+
),
61+
mcp.WithString("path_filter",
62+
mcp.Description("Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)"),
63+
),
64+
),
65+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
66+
owner, err := RequiredParam[string](request, "owner")
67+
if err != nil {
68+
return mcp.NewToolResultError(err.Error()), nil
69+
}
70+
repo, err := RequiredParam[string](request, "repo")
71+
if err != nil {
72+
return mcp.NewToolResultError(err.Error()), nil
73+
}
74+
treeSHA, err := OptionalParam[string](request, "tree_sha")
75+
if err != nil {
76+
return mcp.NewToolResultError(err.Error()), nil
77+
}
78+
recursive, err := OptionalBoolParamWithDefault(request, "recursive", false)
79+
if err != nil {
80+
return mcp.NewToolResultError(err.Error()), nil
81+
}
82+
pathFilter, err := OptionalParam[string](request, "path_filter")
83+
if err != nil {
84+
return mcp.NewToolResultError(err.Error()), nil
85+
}
86+
87+
client, err := getClient(ctx)
88+
if err != nil {
89+
return mcp.NewToolResultError("failed to get GitHub client"), nil
90+
}
91+
92+
// If no tree_sha is provided, use the repository's default branch
93+
if treeSHA == "" {
94+
repoInfo, repoResp, err := client.Repositories.Get(ctx, owner, repo)
95+
if err != nil {
96+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
97+
"failed to get repository info",
98+
repoResp,
99+
err,
100+
), nil
101+
}
102+
treeSHA = *repoInfo.DefaultBranch
103+
}
104+
105+
// Get the tree using the GitHub Git Tree API
106+
tree, resp, err := client.Git.GetTree(ctx, owner, repo, treeSHA, recursive)
107+
if err != nil {
108+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
109+
"failed to get repository tree",
110+
resp,
111+
err,
112+
), nil
113+
}
114+
defer func() { _ = resp.Body.Close() }()
115+
116+
// Filter tree entries if path_filter is provided
117+
var filteredEntries []*github.TreeEntry
118+
if pathFilter != "" {
119+
for _, entry := range tree.Entries {
120+
if strings.HasPrefix(entry.GetPath(), pathFilter) {
121+
filteredEntries = append(filteredEntries, entry)
122+
}
123+
}
124+
} else {
125+
filteredEntries = tree.Entries
126+
}
127+
128+
treeEntries := make([]TreeEntryResponse, len(filteredEntries))
129+
for i, entry := range filteredEntries {
130+
treeEntries[i] = TreeEntryResponse{
131+
Path: entry.GetPath(),
132+
Type: entry.GetType(),
133+
Mode: entry.GetMode(),
134+
SHA: entry.GetSHA(),
135+
URL: entry.GetURL(),
136+
}
137+
if entry.Size != nil {
138+
treeEntries[i].Size = entry.Size
139+
}
140+
}
141+
142+
response := TreeResponse{
143+
SHA: *tree.SHA,
144+
Truncated: *tree.Truncated,
145+
Tree: treeEntries,
146+
TreeSHA: treeSHA,
147+
Owner: owner,
148+
Repo: repo,
149+
Recursive: recursive,
150+
Count: len(filteredEntries),
151+
}
152+
153+
r, err := json.Marshal(response)
154+
if err != nil {
155+
return nil, fmt.Errorf("failed to marshal response: %w", err)
156+
}
157+
158+
return mcp.NewToolResultText(string(r)), nil
159+
}
160+
}

0 commit comments

Comments
 (0)