Skip to content

Commit 69e37b5

Browse files
committed
Add ability to view branches for a repo #141
1 parent 86fbc85 commit 69e37b5

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

pkg/github/repositories.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,64 @@ func ListCommits(client *github.Client, t translations.TranslationHelperFunc) (t
7979
}
8080
}
8181

82+
// ListBranches creates a tool to list branches in a GitHub repository.
83+
func ListBranches(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
84+
return mcp.NewTool("list_branches",
85+
mcp.WithDescription(t("TOOL_LIST_BRANCHES_DESCRIPTION", "List branches in a GitHub repository")),
86+
mcp.WithString("owner",
87+
mcp.Required(),
88+
mcp.Description("Repository owner"),
89+
),
90+
mcp.WithString("repo",
91+
mcp.Required(),
92+
mcp.Description("Repository name"),
93+
),
94+
WithPagination(),
95+
),
96+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
97+
owner, err := requiredParam[string](request, "owner")
98+
if err != nil {
99+
return mcp.NewToolResultError(err.Error()), nil
100+
}
101+
repo, err := requiredParam[string](request, "repo")
102+
if err != nil {
103+
return mcp.NewToolResultError(err.Error()), nil
104+
}
105+
pagination, err := OptionalPaginationParams(request)
106+
if err != nil {
107+
return mcp.NewToolResultError(err.Error()), nil
108+
}
109+
110+
opts := &github.BranchListOptions{
111+
ListOptions: github.ListOptions{
112+
Page: pagination.page,
113+
PerPage: pagination.perPage,
114+
},
115+
}
116+
117+
branches, resp, err := client.Repositories.ListBranches(ctx, owner, repo, opts)
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to list branches: %w", err)
120+
}
121+
defer func() { _ = resp.Body.Close() }()
122+
123+
if resp.StatusCode != http.StatusOK {
124+
body, err := io.ReadAll(resp.Body)
125+
if err != nil {
126+
return nil, fmt.Errorf("failed to read response body: %w", err)
127+
}
128+
return mcp.NewToolResultError(fmt.Sprintf("failed to list branches: %s", string(body))), nil
129+
}
130+
131+
r, err := json.Marshal(branches)
132+
if err != nil {
133+
return nil, fmt.Errorf("failed to marshal response: %w", err)
134+
}
135+
136+
return mcp.NewToolResultText(string(r)), nil
137+
}
138+
}
139+
82140
// CreateOrUpdateFile creates a tool to create or update a file in a GitHub repository.
83141
func CreateOrUpdateFile(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
84142
return mcp.NewTool("create_or_update_file",

pkg/github/repositories_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"encoding/json"
66
"net/http"
7+
"net/http/httptest"
8+
"net/url"
79
"testing"
810
"time"
911

@@ -1293,3 +1295,104 @@ func Test_PushFiles(t *testing.T) {
12931295
})
12941296
}
12951297
}
1298+
1299+
func Test_ListBranches(t *testing.T) {
1300+
// Create a test server
1301+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1302+
assert.Equal(t, "/repos/owner/repo/branches", r.URL.Path)
1303+
assert.Equal(t, "GET", r.Method)
1304+
1305+
// Check query parameters
1306+
query := r.URL.Query()
1307+
if page := query.Get("page"); page != "" {
1308+
assert.Equal(t, "2", page)
1309+
}
1310+
if perPage := query.Get("per_page"); perPage != "" {
1311+
assert.Equal(t, "30", perPage)
1312+
}
1313+
1314+
// Return mock branches
1315+
mockBranches := []github.Branch{
1316+
{Name: github.String("main")},
1317+
{Name: github.String("develop")},
1318+
}
1319+
mockResponse(t, http.StatusOK, mockBranches)(w, r)
1320+
}))
1321+
defer ts.Close()
1322+
1323+
// Create a GitHub client using the test server URL
1324+
client := github.NewClient(nil)
1325+
client.BaseURL, _ = url.Parse(ts.URL + "/")
1326+
1327+
// Create the tool
1328+
tool, handler := ListBranches(client, translations.NullTranslationHelper)
1329+
1330+
// Test cases
1331+
tests := []struct {
1332+
name string
1333+
args map[string]interface{}
1334+
wantErr bool
1335+
errContains string
1336+
}{
1337+
{
1338+
name: "success",
1339+
args: map[string]interface{}{
1340+
"owner": "owner",
1341+
"repo": "repo",
1342+
"page": float64(2),
1343+
},
1344+
wantErr: false,
1345+
},
1346+
{
1347+
name: "missing owner",
1348+
args: map[string]interface{}{
1349+
"repo": "repo",
1350+
},
1351+
wantErr: true,
1352+
errContains: "missing required parameter: owner",
1353+
},
1354+
{
1355+
name: "missing repo",
1356+
args: map[string]interface{}{
1357+
"owner": "owner",
1358+
},
1359+
wantErr: true,
1360+
errContains: "missing required parameter: repo",
1361+
},
1362+
}
1363+
1364+
for _, tt := range tests {
1365+
t.Run(tt.name, func(t *testing.T) {
1366+
// Create request
1367+
request := createMCPRequest(tt.args)
1368+
1369+
// Call handler
1370+
result, err := handler(context.Background(), request)
1371+
if tt.wantErr {
1372+
require.Error(t, err)
1373+
if tt.errContains != "" {
1374+
assert.Contains(t, err.Error(), tt.errContains)
1375+
}
1376+
return
1377+
}
1378+
1379+
require.NoError(t, err)
1380+
textContent := getTextResult(t, result)
1381+
1382+
// Verify response
1383+
var branches []github.Branch
1384+
err = json.Unmarshal([]byte(textContent.Text), &branches)
1385+
require.NoError(t, err)
1386+
assert.Len(t, branches, 2)
1387+
assert.Equal(t, "main", *branches[0].Name)
1388+
assert.Equal(t, "develop", *branches[1].Name)
1389+
})
1390+
}
1391+
1392+
// Verify tool definition
1393+
assert.Equal(t, "list_branches", tool.Name)
1394+
assert.Contains(t, tool.InputSchema.Required, "owner")
1395+
assert.Contains(t, tool.InputSchema.Required, "repo")
1396+
assert.NotContains(t, tool.InputSchema.Required, "page")
1397+
assert.NotContains(t, tool.InputSchema.Required, "perPage")
1398+
}

pkg/github/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func NewServer(client *github.Client, version string, readOnly bool, t translati
6060
s.AddTool(SearchRepositories(client, t))
6161
s.AddTool(GetFileContents(client, t))
6262
s.AddTool(ListCommits(client, t))
63+
s.AddTool(ListBranches(client, t))
6364
if !readOnly {
6465
s.AddTool(CreateOrUpdateFile(client, t))
6566
s.AddTool(CreateRepository(client, t))

0 commit comments

Comments
 (0)