Skip to content

Add ability to manage and list starred repositories #424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,15 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
- `page`: Page number (number, optional)
- `perPage`: Results per page (number, optional)

- **is_repository_starred** - Check if a repository is starred by the authenticated user
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)

- **toggle_repository_star** - Star or unstar a repository for the authenticated user
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
- `star`: True to star, false to unstar the repository (boolean, required)

- **create_repository** - Create a new GitHub repository
- `name`: Repository name (string, required)
- `description`: Repository description (string, optional)
Expand Down Expand Up @@ -550,6 +559,12 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
- `page`: Page number (number, optional)
- `perPage`: Results per page (number, optional)

- **list_starred_repositories** - List repositories starred by the authenticated user
- `sort`: How to sort the results ('created' or 'updated') (string, optional)
- `direction`: Direction to sort ('asc' or 'desc') (string, optional)
- `page`: Page number (number, optional)
- `perPage`: Results per page (number, optional)

### Code Scanning

- **get_code_scanning_alert** - Get a code scanning alert
Expand Down
126 changes: 126 additions & 0 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,132 @@ func ForkRepository(getClient GetClientFn, t translations.TranslationHelperFunc)
}
}

// IsRepositoryStarred creates a tool to check if a repository is starred by the authenticated user.
func IsRepositoryStarred(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("is_repository_starred",
mcp.WithDescription(t("TOOL_IS_REPOSITORY_STARRED_DESCRIPTION", "Check if a GitHub repository is starred by the authenticated user")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: t("TOOL_IS_REPOSITORY_STARRED_USER_TITLE", "Check if repository is starred"),
ReadOnlyHint: toBoolPtr(true),
}),
mcp.WithString("owner",
mcp.Required(),
mcp.Description("Repository owner"),
),
mcp.WithString("repo",
mcp.Required(),
mcp.Description("Repository name"),
),
),
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
}

client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}

isStarred, resp, err := client.Activity.IsStarred(ctx, owner, repo)
if err != nil && resp == nil {
return nil, fmt.Errorf("failed to check if repository is starred: %w", err)
}
if resp != nil {
defer func() { _ = resp.Body.Close() }()
}

return mcp.NewToolResultText(fmt.Sprintf(`{"is_starred": %t}`, isStarred)), nil
}
}

// ToggleRepositoryStar creates a tool to star or unstar a repository.
func ToggleRepositoryStar(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("toggle_repository_star",
mcp.WithDescription(t("TOOL_TOGGLE_REPOSITORY_STAR_DESCRIPTION", "Star or unstar a GitHub repository with your account")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: t("TOOL_TOGGLE_REPOSITORY_STAR_USER_TITLE", "Star/unstar repository"),
ReadOnlyHint: toBoolPtr(false),
}),
mcp.WithString("owner",
mcp.Required(),
mcp.Description("Repository owner"),
),
mcp.WithString("repo",
mcp.Required(),
mcp.Description("Repository name"),
),
mcp.WithString("star",
mcp.Required(),
mcp.Description("'true' to star, 'false' to unstar the repository"),
),
),
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
}
starStr, err := requiredParam[string](request, "star")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

var star bool
switch starStr {
case "true":
star = true
case "false":
star = false
default:
return mcp.NewToolResultError("parameter 'star' must be exactly 'true' or 'false'"), nil
}

client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}

var resp *github.Response
var action string

if star {
resp, err = client.Activity.Star(ctx, owner, repo)
action = "star"
} else {
resp, err = client.Activity.Unstar(ctx, owner, repo)
action = "unstar"
}

if err != nil {
return nil, fmt.Errorf("failed to %s repository: %w", action, err)
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusNoContent {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to %s repository: %s", action, string(body))), nil
}

resultAction := "starred"
if !star {
resultAction = "unstarred"
}
return mcp.NewToolResultText(fmt.Sprintf("Successfully %s repository %s/%s", resultAction, owner, repo)), nil
}
}

// DeleteFile creates a tool to delete a file in a GitHub repository.
// This tool uses a more roundabout way of deleting a file than just using the client.Repositories.DeleteFile.
// This is because REST file deletion endpoint (and client.Repositories.DeleteFile) don't add commit signing to the deletion commit,
Expand Down
Loading