Skip to content

Commit 010cf9b

Browse files
Copilottonytrg
andauthored
Add starred repository support to GitHub MCP server (#1078)
* Initial plan * Initial exploration and planning for starred repository support Co-authored-by: tonytrg <[email protected]> * Implement starred repository functionality with comprehensive tests - Add ListStarredRepositories tool for listing starred repos - Add StarRepository tool for starring repositories - Add UnstarRepository tool for unstarring repositories - Update MinimalRepository struct with StarredAt field - Add comprehensive test coverage for all new functionality - Register new tools in the repos toolset Co-authored-by: tonytrg <[email protected]> * Update README documentation with starred repository tools - Add list_starred_repositories tool documentation - Add star_repository tool documentation - Add unstar_repository tool documentation - Include comprehensive parameter descriptions for all new tools Co-authored-by: tonytrg <[email protected]> * Add starred repository support to GitHub MCP server Co-authored-by: tonytrg <[email protected]> * remove starredat from minimal view * update descriptions * remove bin * dont commit the binary --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: tonytrg <[email protected]> Co-authored-by: tonytrg <[email protected]>
1 parent b6486a3 commit 010cf9b

File tree

9 files changed

+670
-1
lines changed

9 files changed

+670
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ vendor
1414
bin/
1515

1616
# macOS
17-
.DS_Store
17+
.DS_Store
18+
19+
# binary
20+
github-mcp-server

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,13 @@ The following sets of tools are available (all are on by default):
877877
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
878878
- `repo`: Repository name (string, required)
879879

880+
- **list_starred_repositories** - List starred repositories
881+
- `direction`: The direction to sort the results by. (string, optional)
882+
- `page`: Page number for pagination (min 1) (number, optional)
883+
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
884+
- `sort`: How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to). (string, optional)
885+
- `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional)
886+
880887
- **list_tags** - List tags
881888
- `owner`: Repository owner (string, required)
882889
- `page`: Page number for pagination (min 1) (number, optional)
@@ -903,6 +910,14 @@ The following sets of tools are available (all are on by default):
903910
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
904911
- `query`: Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering. (string, required)
905912

913+
- **star_repository** - Star repository
914+
- `owner`: Repository owner (string, required)
915+
- `repo`: Repository name (string, required)
916+
917+
- **unstar_repository** - Unstar repository
918+
- `owner`: Repository owner (string, required)
919+
- `repo`: Repository name (string, required)
920+
906921
</details>
907922

908923
<details>

github-mcp-server

-17.5 MB
Binary file not shown.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"annotations": {
3+
"title": "List starred repositories",
4+
"readOnlyHint": true
5+
},
6+
"description": "List starred repositories",
7+
"inputSchema": {
8+
"properties": {
9+
"direction": {
10+
"description": "The direction to sort the results by.",
11+
"enum": [
12+
"asc",
13+
"desc"
14+
],
15+
"type": "string"
16+
},
17+
"page": {
18+
"description": "Page number for pagination (min 1)",
19+
"minimum": 1,
20+
"type": "number"
21+
},
22+
"perPage": {
23+
"description": "Results per page for pagination (min 1, max 100)",
24+
"maximum": 100,
25+
"minimum": 1,
26+
"type": "number"
27+
},
28+
"sort": {
29+
"description": "How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to).",
30+
"enum": [
31+
"created",
32+
"updated"
33+
],
34+
"type": "string"
35+
},
36+
"username": {
37+
"description": "Username to list starred repositories for. Defaults to the authenticated user.",
38+
"type": "string"
39+
}
40+
},
41+
"type": "object"
42+
},
43+
"name": "list_starred_repositories"
44+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"annotations": {
3+
"title": "Star repository",
4+
"readOnlyHint": false
5+
},
6+
"description": "Star a GitHub repository",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description": "Repository owner",
11+
"type": "string"
12+
},
13+
"repo": {
14+
"description": "Repository name",
15+
"type": "string"
16+
}
17+
},
18+
"required": [
19+
"owner",
20+
"repo"
21+
],
22+
"type": "object"
23+
},
24+
"name": "star_repository"
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"annotations": {
3+
"title": "Unstar repository",
4+
"readOnlyHint": false
5+
},
6+
"description": "Unstar a GitHub repository",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description": "Repository owner",
11+
"type": "string"
12+
},
13+
"repo": {
14+
"description": "Repository name",
15+
"type": "string"
16+
}
17+
},
18+
"required": [
19+
"owner",
20+
"repo"
21+
],
22+
"type": "object"
23+
},
24+
"name": "unstar_repository"
25+
}

pkg/github/repositories.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,3 +1683,231 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner
16831683
sha = reference.GetObject().GetSHA()
16841684
return &raw.ContentOpts{Ref: ref, SHA: sha}, nil
16851685
}
1686+
1687+
// ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user.
1688+
func ListStarredRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1689+
return mcp.NewTool("list_starred_repositories",
1690+
mcp.WithDescription(t("TOOL_LIST_STARRED_REPOSITORIES_DESCRIPTION", "List starred repositories")),
1691+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
1692+
Title: t("TOOL_LIST_STARRED_REPOSITORIES_USER_TITLE", "List starred repositories"),
1693+
ReadOnlyHint: ToBoolPtr(true),
1694+
}),
1695+
mcp.WithString("username",
1696+
mcp.Description("Username to list starred repositories for. Defaults to the authenticated user."),
1697+
),
1698+
mcp.WithString("sort",
1699+
mcp.Description("How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to)."),
1700+
mcp.Enum("created", "updated"),
1701+
),
1702+
mcp.WithString("direction",
1703+
mcp.Description("The direction to sort the results by."),
1704+
mcp.Enum("asc", "desc"),
1705+
),
1706+
WithPagination(),
1707+
),
1708+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
1709+
username, err := OptionalParam[string](request, "username")
1710+
if err != nil {
1711+
return mcp.NewToolResultError(err.Error()), nil
1712+
}
1713+
sort, err := OptionalParam[string](request, "sort")
1714+
if err != nil {
1715+
return mcp.NewToolResultError(err.Error()), nil
1716+
}
1717+
direction, err := OptionalParam[string](request, "direction")
1718+
if err != nil {
1719+
return mcp.NewToolResultError(err.Error()), nil
1720+
}
1721+
pagination, err := OptionalPaginationParams(request)
1722+
if err != nil {
1723+
return mcp.NewToolResultError(err.Error()), nil
1724+
}
1725+
1726+
opts := &github.ActivityListStarredOptions{
1727+
ListOptions: github.ListOptions{
1728+
Page: pagination.Page,
1729+
PerPage: pagination.PerPage,
1730+
},
1731+
}
1732+
if sort != "" {
1733+
opts.Sort = sort
1734+
}
1735+
if direction != "" {
1736+
opts.Direction = direction
1737+
}
1738+
1739+
client, err := getClient(ctx)
1740+
if err != nil {
1741+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
1742+
}
1743+
1744+
var repos []*github.StarredRepository
1745+
var resp *github.Response
1746+
if username == "" {
1747+
// List starred repositories for the authenticated user
1748+
repos, resp, err = client.Activity.ListStarred(ctx, "", opts)
1749+
} else {
1750+
// List starred repositories for a specific user
1751+
repos, resp, err = client.Activity.ListStarred(ctx, username, opts)
1752+
}
1753+
1754+
if err != nil {
1755+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
1756+
fmt.Sprintf("failed to list starred repositories for user '%s'", username),
1757+
resp,
1758+
err,
1759+
), nil
1760+
}
1761+
defer func() { _ = resp.Body.Close() }()
1762+
1763+
if resp.StatusCode != 200 {
1764+
body, err := io.ReadAll(resp.Body)
1765+
if err != nil {
1766+
return nil, fmt.Errorf("failed to read response body: %w", err)
1767+
}
1768+
return mcp.NewToolResultError(fmt.Sprintf("failed to list starred repositories: %s", string(body))), nil
1769+
}
1770+
1771+
// Convert to minimal format
1772+
minimalRepos := make([]MinimalRepository, 0, len(repos))
1773+
for _, starredRepo := range repos {
1774+
repo := starredRepo.Repository
1775+
minimalRepo := MinimalRepository{
1776+
ID: repo.GetID(),
1777+
Name: repo.GetName(),
1778+
FullName: repo.GetFullName(),
1779+
Description: repo.GetDescription(),
1780+
HTMLURL: repo.GetHTMLURL(),
1781+
Language: repo.GetLanguage(),
1782+
Stars: repo.GetStargazersCount(),
1783+
Forks: repo.GetForksCount(),
1784+
OpenIssues: repo.GetOpenIssuesCount(),
1785+
Private: repo.GetPrivate(),
1786+
Fork: repo.GetFork(),
1787+
Archived: repo.GetArchived(),
1788+
DefaultBranch: repo.GetDefaultBranch(),
1789+
}
1790+
1791+
if repo.UpdatedAt != nil {
1792+
minimalRepo.UpdatedAt = repo.UpdatedAt.Format("2006-01-02T15:04:05Z")
1793+
}
1794+
1795+
minimalRepos = append(minimalRepos, minimalRepo)
1796+
}
1797+
1798+
r, err := json.Marshal(minimalRepos)
1799+
if err != nil {
1800+
return nil, fmt.Errorf("failed to marshal starred repositories: %w", err)
1801+
}
1802+
1803+
return mcp.NewToolResultText(string(r)), nil
1804+
}
1805+
}
1806+
1807+
// StarRepository creates a tool to star a repository.
1808+
func StarRepository(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1809+
return mcp.NewTool("star_repository",
1810+
mcp.WithDescription(t("TOOL_STAR_REPOSITORY_DESCRIPTION", "Star a GitHub repository")),
1811+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
1812+
Title: t("TOOL_STAR_REPOSITORY_USER_TITLE", "Star repository"),
1813+
ReadOnlyHint: ToBoolPtr(false),
1814+
}),
1815+
mcp.WithString("owner",
1816+
mcp.Required(),
1817+
mcp.Description("Repository owner"),
1818+
),
1819+
mcp.WithString("repo",
1820+
mcp.Required(),
1821+
mcp.Description("Repository name"),
1822+
),
1823+
),
1824+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
1825+
owner, err := RequiredParam[string](request, "owner")
1826+
if err != nil {
1827+
return mcp.NewToolResultError(err.Error()), nil
1828+
}
1829+
repo, err := RequiredParam[string](request, "repo")
1830+
if err != nil {
1831+
return mcp.NewToolResultError(err.Error()), nil
1832+
}
1833+
1834+
client, err := getClient(ctx)
1835+
if err != nil {
1836+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
1837+
}
1838+
1839+
resp, err := client.Activity.Star(ctx, owner, repo)
1840+
if err != nil {
1841+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
1842+
fmt.Sprintf("failed to star repository %s/%s", owner, repo),
1843+
resp,
1844+
err,
1845+
), nil
1846+
}
1847+
defer func() { _ = resp.Body.Close() }()
1848+
1849+
if resp.StatusCode != 204 {
1850+
body, err := io.ReadAll(resp.Body)
1851+
if err != nil {
1852+
return nil, fmt.Errorf("failed to read response body: %w", err)
1853+
}
1854+
return mcp.NewToolResultError(fmt.Sprintf("failed to star repository: %s", string(body))), nil
1855+
}
1856+
1857+
return mcp.NewToolResultText(fmt.Sprintf("Successfully starred repository %s/%s", owner, repo)), nil
1858+
}
1859+
}
1860+
1861+
// UnstarRepository creates a tool to unstar a repository.
1862+
func UnstarRepository(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1863+
return mcp.NewTool("unstar_repository",
1864+
mcp.WithDescription(t("TOOL_UNSTAR_REPOSITORY_DESCRIPTION", "Unstar a GitHub repository")),
1865+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
1866+
Title: t("TOOL_UNSTAR_REPOSITORY_USER_TITLE", "Unstar repository"),
1867+
ReadOnlyHint: ToBoolPtr(false),
1868+
}),
1869+
mcp.WithString("owner",
1870+
mcp.Required(),
1871+
mcp.Description("Repository owner"),
1872+
),
1873+
mcp.WithString("repo",
1874+
mcp.Required(),
1875+
mcp.Description("Repository name"),
1876+
),
1877+
),
1878+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
1879+
owner, err := RequiredParam[string](request, "owner")
1880+
if err != nil {
1881+
return mcp.NewToolResultError(err.Error()), nil
1882+
}
1883+
repo, err := RequiredParam[string](request, "repo")
1884+
if err != nil {
1885+
return mcp.NewToolResultError(err.Error()), nil
1886+
}
1887+
1888+
client, err := getClient(ctx)
1889+
if err != nil {
1890+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
1891+
}
1892+
1893+
resp, err := client.Activity.Unstar(ctx, owner, repo)
1894+
if err != nil {
1895+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
1896+
fmt.Sprintf("failed to unstar repository %s/%s", owner, repo),
1897+
resp,
1898+
err,
1899+
), nil
1900+
}
1901+
defer func() { _ = resp.Body.Close() }()
1902+
1903+
if resp.StatusCode != 204 {
1904+
body, err := io.ReadAll(resp.Body)
1905+
if err != nil {
1906+
return nil, fmt.Errorf("failed to read response body: %w", err)
1907+
}
1908+
return mcp.NewToolResultError(fmt.Sprintf("failed to unstar repository: %s", string(body))), nil
1909+
}
1910+
1911+
return mcp.NewToolResultText(fmt.Sprintf("Successfully unstarred repository %s/%s", owner, repo)), nil
1912+
}
1913+
}

0 commit comments

Comments
 (0)