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>
This commit is contained in:
@@ -400,6 +400,7 @@ The following sets of tools are available:
|
||||
| `discussions` | GitHub Discussions related tools |
|
||||
| `experiments` | Experimental features that are not considered stable yet |
|
||||
| `gists` | GitHub Gist related tools |
|
||||
| `git` | GitHub Git API related tools for low-level Git operations |
|
||||
| `issues` | GitHub Issues related tools |
|
||||
| `labels` | GitHub Labels related tools |
|
||||
| `notifications` | GitHub Notifications related tools |
|
||||
@@ -630,6 +631,19 @@ The following sets of tools are available:
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Git</summary>
|
||||
|
||||
- **get_repository_tree** - Get repository tree
|
||||
- `owner`: Repository owner (username or organization) (string, required)
|
||||
- `path_filter`: Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory) (string, optional)
|
||||
- `recursive`: Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false (boolean, optional)
|
||||
- `repo`: Repository name (string, required)
|
||||
- `tree_sha`: The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch (string, optional)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Issues</summary>
|
||||
|
||||
- **add_issue_comment** - Add comment to issue
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -26,6 +26,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
|
||||
@@ -3,6 +3,7 @@ module github.com/github/github-mcp-server
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/google/go-github/v76 v76.0.0
|
||||
github.com/google/go-github/v77 v77.0.0
|
||||
github.com/josephburnett/jd v1.9.2
|
||||
github.com/mark3labs/mcp-go v0.36.0
|
||||
|
||||
@@ -26,6 +26,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
|
||||
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
|
||||
github.com/google/go-github/v76 v76.0.0 h1:MCa9VQn+VG5GG7Y7BAkBvSRUN3o+QpaEOuZwFPJmdFA=
|
||||
github.com/google/go-github/v76 v76.0.0/go.mod h1:38+d/8pYDO4fBLYfBhXF5EKO0wA3UkXBjfmQapFsNCQ=
|
||||
github.com/google/go-github/v77 v77.0.0 h1:9DsKKbZqil5y/4Z9mNpZDQnpli6PJbqipSuuNdcbjwI=
|
||||
github.com/google/go-github/v77 v77.0.0/go.mod h1:c8VmGXRUmaZUqbctUcGEDWYnMrtzZfJhDSylEf1wfmA=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"annotations": {
|
||||
"title": "Get repository tree",
|
||||
"readOnlyHint": true
|
||||
},
|
||||
"description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"owner": {
|
||||
"description": "Repository owner (username or organization)",
|
||||
"type": "string"
|
||||
},
|
||||
"path_filter": {
|
||||
"description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
|
||||
"type": "string"
|
||||
},
|
||||
"recursive": {
|
||||
"default": false,
|
||||
"description": "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
|
||||
"type": "boolean"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repository name",
|
||||
"type": "string"
|
||||
},
|
||||
"tree_sha": {
|
||||
"description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"owner",
|
||||
"repo"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_repository_tree"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ghErrors "github.com/github/github-mcp-server/pkg/errors"
|
||||
"github.com/github/github-mcp-server/pkg/translations"
|
||||
"github.com/google/go-github/v77/github"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
// TreeEntryResponse represents a single entry in a Git tree.
|
||||
type TreeEntryResponse struct {
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
Size *int `json:"size,omitempty"`
|
||||
Mode string `json:"mode"`
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// TreeResponse represents the response structure for a Git tree.
|
||||
type TreeResponse struct {
|
||||
SHA string `json:"sha"`
|
||||
Truncated bool `json:"truncated"`
|
||||
Tree []TreeEntryResponse `json:"tree"`
|
||||
TreeSHA string `json:"tree_sha"`
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Recursive bool `json:"recursive"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// GetRepositoryTree creates a tool to get the tree structure of a GitHub repository.
|
||||
func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
|
||||
return mcp.NewTool("get_repository_tree",
|
||||
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")),
|
||||
mcp.WithToolAnnotation(mcp.ToolAnnotation{
|
||||
Title: t("TOOL_GET_REPOSITORY_TREE_USER_TITLE", "Get repository tree"),
|
||||
ReadOnlyHint: ToBoolPtr(true),
|
||||
}),
|
||||
mcp.WithString("owner",
|
||||
mcp.Required(),
|
||||
mcp.Description("Repository owner (username or organization)"),
|
||||
),
|
||||
mcp.WithString("repo",
|
||||
mcp.Required(),
|
||||
mcp.Description("Repository name"),
|
||||
),
|
||||
mcp.WithString("tree_sha",
|
||||
mcp.Description("The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch"),
|
||||
),
|
||||
mcp.WithBoolean("recursive",
|
||||
mcp.Description("Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false"),
|
||||
mcp.DefaultBool(false),
|
||||
),
|
||||
mcp.WithString("path_filter",
|
||||
mcp.Description("Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)"),
|
||||
),
|
||||
),
|
||||
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := RequiredParam[string](request, "owner")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
repo, err := RequiredParam[string](request, "repo")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
treeSHA, err := OptionalParam[string](request, "tree_sha")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
recursive, err := OptionalBoolParamWithDefault(request, "recursive", false)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
pathFilter, err := OptionalParam[string](request, "path_filter")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
client, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("failed to get GitHub client"), nil
|
||||
}
|
||||
|
||||
// If no tree_sha is provided, use the repository's default branch
|
||||
if treeSHA == "" {
|
||||
repoInfo, repoResp, err := client.Repositories.Get(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return ghErrors.NewGitHubAPIErrorResponse(ctx,
|
||||
"failed to get repository info",
|
||||
repoResp,
|
||||
err,
|
||||
), nil
|
||||
}
|
||||
treeSHA = *repoInfo.DefaultBranch
|
||||
}
|
||||
|
||||
// Get the tree using the GitHub Git Tree API
|
||||
tree, resp, err := client.Git.GetTree(ctx, owner, repo, treeSHA, recursive)
|
||||
if err != nil {
|
||||
return ghErrors.NewGitHubAPIErrorResponse(ctx,
|
||||
"failed to get repository tree",
|
||||
resp,
|
||||
err,
|
||||
), nil
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Filter tree entries if path_filter is provided
|
||||
var filteredEntries []*github.TreeEntry
|
||||
if pathFilter != "" {
|
||||
for _, entry := range tree.Entries {
|
||||
if strings.HasPrefix(entry.GetPath(), pathFilter) {
|
||||
filteredEntries = append(filteredEntries, entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredEntries = tree.Entries
|
||||
}
|
||||
|
||||
treeEntries := make([]TreeEntryResponse, len(filteredEntries))
|
||||
for i, entry := range filteredEntries {
|
||||
treeEntries[i] = TreeEntryResponse{
|
||||
Path: entry.GetPath(),
|
||||
Type: entry.GetType(),
|
||||
Mode: entry.GetMode(),
|
||||
SHA: entry.GetSHA(),
|
||||
URL: entry.GetURL(),
|
||||
}
|
||||
if entry.Size != nil {
|
||||
treeEntries[i].Size = entry.Size
|
||||
}
|
||||
}
|
||||
|
||||
response := TreeResponse{
|
||||
SHA: *tree.SHA,
|
||||
Truncated: *tree.Truncated,
|
||||
Tree: treeEntries,
|
||||
TreeSHA: treeSHA,
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
Recursive: recursive,
|
||||
Count: len(filteredEntries),
|
||||
}
|
||||
|
||||
r, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(r)), nil
|
||||
}
|
||||
}
|
||||
@@ -3237,3 +3237,178 @@ func Test_UnstarRepository(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetRepositoryTree(t *testing.T) {
|
||||
// Verify tool definition once
|
||||
mockClient := github.NewClient(nil)
|
||||
tool, _ := GetRepositoryTree(stubGetClientFn(mockClient), translations.NullTranslationHelper)
|
||||
require.NoError(t, toolsnaps.Test(tool.Name, tool))
|
||||
|
||||
assert.Equal(t, "get_repository_tree", tool.Name)
|
||||
assert.NotEmpty(t, tool.Description)
|
||||
assert.Contains(t, tool.InputSchema.Properties, "owner")
|
||||
assert.Contains(t, tool.InputSchema.Properties, "repo")
|
||||
assert.Contains(t, tool.InputSchema.Properties, "tree_sha")
|
||||
assert.Contains(t, tool.InputSchema.Properties, "recursive")
|
||||
assert.Contains(t, tool.InputSchema.Properties, "path_filter")
|
||||
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"})
|
||||
|
||||
// Setup mock data
|
||||
mockRepo := &github.Repository{
|
||||
DefaultBranch: github.Ptr("main"),
|
||||
}
|
||||
mockTree := &github.Tree{
|
||||
SHA: github.Ptr("abc123"),
|
||||
Truncated: github.Ptr(false),
|
||||
Entries: []*github.TreeEntry{
|
||||
{
|
||||
Path: github.Ptr("README.md"),
|
||||
Mode: github.Ptr("100644"),
|
||||
Type: github.Ptr("blob"),
|
||||
SHA: github.Ptr("file1sha"),
|
||||
Size: github.Ptr(123),
|
||||
URL: github.Ptr("https://api.github.com/repos/owner/repo/git/blobs/file1sha"),
|
||||
},
|
||||
{
|
||||
Path: github.Ptr("src/main.go"),
|
||||
Mode: github.Ptr("100644"),
|
||||
Type: github.Ptr("blob"),
|
||||
SHA: github.Ptr("file2sha"),
|
||||
Size: github.Ptr(456),
|
||||
URL: github.Ptr("https://api.github.com/repos/owner/repo/git/blobs/file2sha"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mockedClient *http.Client
|
||||
requestArgs map[string]interface{}
|
||||
expectError bool
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "successfully get repository tree",
|
||||
mockedClient: mock.NewMockedHTTPClient(
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposByOwnerByRepo,
|
||||
mockResponse(t, http.StatusOK, mockRepo),
|
||||
),
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposGitTreesByOwnerByRepoByTreeSha,
|
||||
mockResponse(t, http.StatusOK, mockTree),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]interface{}{
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successfully get repository tree with path filter",
|
||||
mockedClient: mock.NewMockedHTTPClient(
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposByOwnerByRepo,
|
||||
mockResponse(t, http.StatusOK, mockRepo),
|
||||
),
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposGitTreesByOwnerByRepoByTreeSha,
|
||||
mockResponse(t, http.StatusOK, mockTree),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]interface{}{
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
"path_filter": "src/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "repository not found",
|
||||
mockedClient: mock.NewMockedHTTPClient(
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposByOwnerByRepo,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message": "Not Found"}`))
|
||||
}),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]interface{}{
|
||||
"owner": "owner",
|
||||
"repo": "nonexistent",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "failed to get repository info",
|
||||
},
|
||||
{
|
||||
name: "tree not found",
|
||||
mockedClient: mock.NewMockedHTTPClient(
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposByOwnerByRepo,
|
||||
mockResponse(t, http.StatusOK, mockRepo),
|
||||
),
|
||||
mock.WithRequestMatchHandler(
|
||||
mock.GetReposGitTreesByOwnerByRepoByTreeSha,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message": "Not Found"}`))
|
||||
}),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]interface{}{
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "failed to get repository tree",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, handler := GetRepositoryTree(stubGetClientFromHTTPFn(tc.mockedClient), translations.NullTranslationHelper)
|
||||
|
||||
// Create the tool request
|
||||
request := createMCPRequest(tc.requestArgs)
|
||||
|
||||
result, err := handler(context.Background(), request)
|
||||
|
||||
if tc.expectError {
|
||||
require.NoError(t, err)
|
||||
require.True(t, result.IsError)
|
||||
errorContent := getErrorResult(t, result)
|
||||
assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.False(t, result.IsError)
|
||||
|
||||
// Parse the result and get the text content
|
||||
textContent := getTextResult(t, result)
|
||||
|
||||
// Parse the JSON response
|
||||
var treeResponse map[string]interface{}
|
||||
err := json.Unmarshal([]byte(textContent.Text), &treeResponse)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify response structure
|
||||
assert.Equal(t, "owner", treeResponse["owner"])
|
||||
assert.Equal(t, "repo", treeResponse["repo"])
|
||||
assert.Contains(t, treeResponse, "tree")
|
||||
assert.Contains(t, treeResponse, "count")
|
||||
assert.Contains(t, treeResponse, "sha")
|
||||
assert.Contains(t, treeResponse, "truncated")
|
||||
|
||||
// Check filtering if path_filter was provided
|
||||
if pathFilter, exists := tc.requestArgs["path_filter"]; exists {
|
||||
tree := treeResponse["tree"].([]interface{})
|
||||
for _, entry := range tree {
|
||||
entryMap := entry.(map[string]interface{})
|
||||
path := entryMap["path"].(string)
|
||||
assert.True(t, strings.HasPrefix(path, pathFilter.(string)),
|
||||
"Path %s should start with filter %s", path, pathFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ var (
|
||||
ID: "repos",
|
||||
Description: "GitHub Repository related tools",
|
||||
}
|
||||
ToolsetMetadataGit = ToolsetMetadata{
|
||||
ID: "git",
|
||||
Description: "GitHub Git API related tools for low-level Git operations",
|
||||
}
|
||||
ToolsetMetadataIssues = ToolsetMetadata{
|
||||
ID: "issues",
|
||||
Description: "GitHub Issues related tools",
|
||||
@@ -189,6 +193,10 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
|
||||
toolsets.NewServerResourceTemplate(GetRepositoryResourceTagContent(getClient, getRawClient, t)),
|
||||
toolsets.NewServerResourceTemplate(GetRepositoryResourcePrContent(getClient, getRawClient, t)),
|
||||
)
|
||||
git := toolsets.NewToolset(ToolsetMetadataGit.ID, ToolsetMetadataGit.Description).
|
||||
AddReadTools(
|
||||
toolsets.NewServerTool(GetRepositoryTree(getClient, t)),
|
||||
)
|
||||
issues := toolsets.NewToolset(ToolsetMetadataIssues.ID, ToolsetMetadataIssues.Description).
|
||||
AddReadTools(
|
||||
toolsets.NewServerTool(IssueRead(getClient, getGQLClient, t)),
|
||||
@@ -353,6 +361,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
|
||||
// Add toolsets to the group
|
||||
tsg.AddToolset(contextTools)
|
||||
tsg.AddToolset(repos)
|
||||
tsg.AddToolset(git)
|
||||
tsg.AddToolset(issues)
|
||||
tsg.AddToolset(orgs)
|
||||
tsg.AddToolset(users)
|
||||
|
||||
Reference in New Issue
Block a user