Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions pkg/github/__toolsnaps__/get_repository_tree.snap
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"annotations": {
"title": "Get repository tree",
"readOnlyHint": true
"readOnlyHint": true,
"title": "Get repository tree"
},
"description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA",
"inputSchema": {
"type": "object",
"required": [
"owner",
"repo"
],
"properties": {
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
"type": "string",
"description": "Repository owner (username or organization)"
},
"path_filter": {
"description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
"type": "string"
"type": "string",
"description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)"
},
"recursive": {
"default": false,
"type": "boolean",
"description": "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
"type": "boolean"
"default": false
},
"repo": {
"description": "Repository name",
"type": "string"
"type": "string",
"description": "Repository name"
},
"tree_sha": {
"description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
"type": "string"
"type": "string",
"description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
}
},
"name": "get_repository_tree"
}
108 changes: 61 additions & 47 deletions pkg/github/git.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build ignore

package github

import (
Expand All @@ -10,9 +8,10 @@ import (

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/github/github-mcp-server/pkg/utils"
"github.com/google/go-github/v79/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

// TreeEntryResponse represents a single entry in a Git tree.
Expand All @@ -38,57 +37,69 @@ type TreeResponse struct {
}

// 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")
func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
tool := mcp.Tool{
Name: "get_repository_tree",
Description: t("TOOL_GET_REPOSITORY_TREE_DESCRIPTION", "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA"),
Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_GET_REPOSITORY_TREE_USER_TITLE", "Get repository tree"),
ReadOnlyHint: true,
},
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"owner": {
Type: "string",
Description: "Repository owner (username or organization)",
},
"repo": {
Type: "string",
Description: "Repository name",
},
"tree_sha": {
Type: "string",
Description: "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
},
"recursive": {
Type: "boolean",
Description: "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
Default: json.RawMessage(`false`),
},
"path_filter": {
Type: "string",
Description: "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
},
},
Required: []string{"owner", "repo"},
},
}

handler := mcp.ToolHandlerFor[map[string]any, any](
func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
owner, err := RequiredParam[string](args, "owner")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
repo, err := RequiredParam[string](request, "repo")
repo, err := RequiredParam[string](args, "repo")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
treeSHA, err := OptionalParam[string](request, "tree_sha")
treeSHA, err := OptionalParam[string](args, "tree_sha")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
recursive, err := OptionalBoolParamWithDefault(request, "recursive", false)
recursive, err := OptionalBoolParamWithDefault(args, "recursive", false)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
pathFilter, err := OptionalParam[string](request, "path_filter")
pathFilter, err := OptionalParam[string](args, "path_filter")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}

client, err := getClient(ctx)
if err != nil {
return mcp.NewToolResultError("failed to get GitHub client"), nil
return utils.NewToolResultError("failed to get GitHub client"), nil, nil
}

// If no tree_sha is provided, use the repository's default branch
Expand All @@ -99,7 +110,7 @@ func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFu
"failed to get repository info",
repoResp,
err,
), nil
), nil, nil
}
treeSHA = *repoInfo.DefaultBranch
}
Expand All @@ -111,7 +122,7 @@ func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFu
"failed to get repository tree",
resp,
err,
), nil
), nil, nil
}
defer func() { _ = resp.Body.Close() }()

Expand Down Expand Up @@ -154,9 +165,12 @@ func GetRepositoryTree(getClient GetClientFn, t translations.TranslationHelperFu

r, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}

return mcp.NewToolResultText(string(r)), nil
}
return utils.NewToolResultText(string(r)), nil, nil
},
)

return tool, handler
}
Loading
Loading