Skip to content

Commit 326c137

Browse files
feat: wire up release tools to use typed inputs
- ListReleases: ListReleasesInput - GetLatestRelease: GetLatestReleaseInput - GetReleaseByTag: GetReleaseByTagInput Updated tests and toolsnaps for all release tools. Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
1 parent c8c1243 commit 326c137

File tree

5 files changed

+143
-261
lines changed

5 files changed

+143
-261
lines changed

pkg/github/__toolsnaps__/get_latest_release.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"type": "string",
2020
"description": "Repository name"
2121
}
22-
}
22+
},
23+
"additionalProperties": false
2324
},
2425
"name": "get_latest_release"
2526
}

pkg/github/__toolsnaps__/get_release_by_tag.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
},
2323
"tag": {
2424
"type": "string",
25-
"description": "Tag name (e.g., 'v1.0.0')"
25+
"description": "Tag name (e.g. 'v1.0.0')"
2626
}
27-
}
27+
},
28+
"additionalProperties": false
2829
},
2930
"name": "get_release_by_tag"
3031
}

pkg/github/__toolsnaps__/list_releases.snap

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,19 @@
1616
"description": "Repository owner"
1717
},
1818
"page": {
19-
"type": "number",
20-
"description": "Page number for pagination (min 1)",
21-
"minimum": 1
19+
"type": "integer",
20+
"description": "Page number for pagination (min 1)"
2221
},
2322
"perPage": {
24-
"type": "number",
25-
"description": "Results per page for pagination (min 1, max 100)",
26-
"minimum": 1,
27-
"maximum": 100
23+
"type": "integer",
24+
"description": "Results per page for pagination (min 1 and max 100)"
2825
},
2926
"repo": {
3027
"type": "string",
3128
"description": "Repository name"
3229
}
33-
}
30+
},
31+
"additionalProperties": false
3432
},
3533
"name": "list_releases"
3634
}

pkg/github/repositories.go

Lines changed: 93 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,218 +1415,131 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.To
14151415
}
14161416

14171417
// ListReleases creates a tool to list releases in a GitHub repository.
1418-
func ListReleases(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
1419-
tool := mcp.Tool{
1420-
Name: "list_releases",
1421-
Description: t("TOOL_LIST_RELEASES_DESCRIPTION", "List releases in a GitHub repository"),
1422-
Annotations: &mcp.ToolAnnotations{
1423-
Title: t("TOOL_LIST_RELEASES_USER_TITLE", "List releases"),
1424-
ReadOnlyHint: true,
1425-
},
1426-
InputSchema: WithPagination(&jsonschema.Schema{
1427-
Type: "object",
1428-
Properties: map[string]*jsonschema.Schema{
1429-
"owner": {
1430-
Type: "string",
1431-
Description: "Repository owner",
1432-
},
1433-
"repo": {
1434-
Type: "string",
1435-
Description: "Repository name",
1436-
},
1418+
func ListReleases(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[ListReleasesInput, any]) {
1419+
return mcp.Tool{
1420+
Name: "list_releases",
1421+
Description: t("TOOL_LIST_RELEASES_DESCRIPTION", "List releases in a GitHub repository"),
1422+
Annotations: &mcp.ToolAnnotations{
1423+
Title: t("TOOL_LIST_RELEASES_USER_TITLE", "List releases"),
1424+
ReadOnlyHint: true,
14371425
},
1438-
Required: []string{"owner", "repo"},
1439-
}),
1440-
}
1441-
1442-
handler := mcp.ToolHandlerFor[map[string]any, any](func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
1443-
owner, err := RequiredParam[string](args, "owner")
1444-
if err != nil {
1445-
return utils.NewToolResultError(err.Error()), nil, nil
1446-
}
1447-
repo, err := RequiredParam[string](args, "repo")
1448-
if err != nil {
1449-
return utils.NewToolResultError(err.Error()), nil, nil
1450-
}
1451-
pagination, err := OptionalPaginationParams(args)
1452-
if err != nil {
1453-
return utils.NewToolResultError(err.Error()), nil, nil
1454-
}
1426+
InputSchema: ListReleasesInput{}.MCPSchema(),
1427+
},
1428+
func(ctx context.Context, _ *mcp.CallToolRequest, input ListReleasesInput) (*mcp.CallToolResult, any, error) {
1429+
opts := &github.ListOptions{
1430+
Page: input.Page,
1431+
PerPage: input.PerPage,
1432+
}
14551433

1456-
opts := &github.ListOptions{
1457-
Page: pagination.Page,
1458-
PerPage: pagination.PerPage,
1459-
}
1434+
client, err := getClient(ctx)
1435+
if err != nil {
1436+
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1437+
}
14601438

1461-
client, err := getClient(ctx)
1462-
if err != nil {
1463-
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1464-
}
1439+
releases, resp, err := client.Repositories.ListReleases(ctx, input.Owner, input.Repo, opts)
1440+
if err != nil {
1441+
return nil, nil, fmt.Errorf("failed to list releases: %w", err)
1442+
}
1443+
defer func() { _ = resp.Body.Close() }()
14651444

1466-
releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
1467-
if err != nil {
1468-
return nil, nil, fmt.Errorf("failed to list releases: %w", err)
1469-
}
1470-
defer func() { _ = resp.Body.Close() }()
1445+
if resp.StatusCode != http.StatusOK {
1446+
body, err := io.ReadAll(resp.Body)
1447+
if err != nil {
1448+
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1449+
}
1450+
return utils.NewToolResultError(fmt.Sprintf("failed to list releases: %s", string(body))), nil, nil
1451+
}
14711452

1472-
if resp.StatusCode != http.StatusOK {
1473-
body, err := io.ReadAll(resp.Body)
1453+
r, err := json.Marshal(releases)
14741454
if err != nil {
1475-
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1455+
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
14761456
}
1477-
return utils.NewToolResultError(fmt.Sprintf("failed to list releases: %s", string(body))), nil, nil
1478-
}
14791457

1480-
r, err := json.Marshal(releases)
1481-
if err != nil {
1482-
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
1458+
return utils.NewToolResultText(string(r)), nil, nil
14831459
}
1484-
1485-
return utils.NewToolResultText(string(r)), nil, nil
1486-
})
1487-
1488-
return tool, handler
14891460
}
14901461

14911462
// GetLatestRelease creates a tool to get the latest release in a GitHub repository.
1492-
func GetLatestRelease(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
1493-
tool := mcp.Tool{
1494-
Name: "get_latest_release",
1495-
Description: t("TOOL_GET_LATEST_RELEASE_DESCRIPTION", "Get the latest release in a GitHub repository"),
1496-
Annotations: &mcp.ToolAnnotations{
1497-
Title: t("TOOL_GET_LATEST_RELEASE_USER_TITLE", "Get latest release"),
1498-
ReadOnlyHint: true,
1499-
},
1500-
InputSchema: &jsonschema.Schema{
1501-
Type: "object",
1502-
Properties: map[string]*jsonschema.Schema{
1503-
"owner": {
1504-
Type: "string",
1505-
Description: "Repository owner",
1506-
},
1507-
"repo": {
1508-
Type: "string",
1509-
Description: "Repository name",
1510-
},
1463+
func GetLatestRelease(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[GetLatestReleaseInput, any]) {
1464+
return mcp.Tool{
1465+
Name: "get_latest_release",
1466+
Description: t("TOOL_GET_LATEST_RELEASE_DESCRIPTION", "Get the latest release in a GitHub repository"),
1467+
Annotations: &mcp.ToolAnnotations{
1468+
Title: t("TOOL_GET_LATEST_RELEASE_USER_TITLE", "Get latest release"),
1469+
ReadOnlyHint: true,
15111470
},
1512-
Required: []string{"owner", "repo"},
1471+
InputSchema: GetLatestReleaseInput{}.MCPSchema(),
15131472
},
1514-
}
1515-
1516-
handler := mcp.ToolHandlerFor[map[string]any, any](func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
1517-
owner, err := RequiredParam[string](args, "owner")
1518-
if err != nil {
1519-
return utils.NewToolResultError(err.Error()), nil, nil
1520-
}
1521-
repo, err := RequiredParam[string](args, "repo")
1522-
if err != nil {
1523-
return utils.NewToolResultError(err.Error()), nil, nil
1524-
}
1473+
func(ctx context.Context, _ *mcp.CallToolRequest, input GetLatestReleaseInput) (*mcp.CallToolResult, any, error) {
1474+
client, err := getClient(ctx)
1475+
if err != nil {
1476+
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1477+
}
15251478

1526-
client, err := getClient(ctx)
1527-
if err != nil {
1528-
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1529-
}
1479+
release, resp, err := client.Repositories.GetLatestRelease(ctx, input.Owner, input.Repo)
1480+
if err != nil {
1481+
return nil, nil, fmt.Errorf("failed to get latest release: %w", err)
1482+
}
1483+
defer func() { _ = resp.Body.Close() }()
15301484

1531-
release, resp, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
1532-
if err != nil {
1533-
return nil, nil, fmt.Errorf("failed to get latest release: %w", err)
1534-
}
1535-
defer func() { _ = resp.Body.Close() }()
1485+
if resp.StatusCode != http.StatusOK {
1486+
body, err := io.ReadAll(resp.Body)
1487+
if err != nil {
1488+
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1489+
}
1490+
return utils.NewToolResultError(fmt.Sprintf("failed to get latest release: %s", string(body))), nil, nil
1491+
}
15361492

1537-
if resp.StatusCode != http.StatusOK {
1538-
body, err := io.ReadAll(resp.Body)
1493+
r, err := json.Marshal(release)
15391494
if err != nil {
1540-
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1495+
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
15411496
}
1542-
return utils.NewToolResultError(fmt.Sprintf("failed to get latest release: %s", string(body))), nil, nil
1543-
}
15441497

1545-
r, err := json.Marshal(release)
1546-
if err != nil {
1547-
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
1498+
return utils.NewToolResultText(string(r)), nil, nil
15481499
}
1549-
1550-
return utils.NewToolResultText(string(r)), nil, nil
1551-
})
1552-
1553-
return tool, handler
15541500
}
15551501

1556-
func GetReleaseByTag(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
1557-
tool := mcp.Tool{
1558-
Name: "get_release_by_tag",
1559-
Description: t("TOOL_GET_RELEASE_BY_TAG_DESCRIPTION", "Get a specific release by its tag name in a GitHub repository"),
1560-
Annotations: &mcp.ToolAnnotations{
1561-
Title: t("TOOL_GET_RELEASE_BY_TAG_USER_TITLE", "Get a release by tag name"),
1562-
ReadOnlyHint: true,
1563-
},
1564-
InputSchema: &jsonschema.Schema{
1565-
Type: "object",
1566-
Properties: map[string]*jsonschema.Schema{
1567-
"owner": {
1568-
Type: "string",
1569-
Description: "Repository owner",
1570-
},
1571-
"repo": {
1572-
Type: "string",
1573-
Description: "Repository name",
1574-
},
1575-
"tag": {
1576-
Type: "string",
1577-
Description: "Tag name (e.g., 'v1.0.0')",
1578-
},
1502+
func GetReleaseByTag(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[GetReleaseByTagInput, any]) {
1503+
return mcp.Tool{
1504+
Name: "get_release_by_tag",
1505+
Description: t("TOOL_GET_RELEASE_BY_TAG_DESCRIPTION", "Get a specific release by its tag name in a GitHub repository"),
1506+
Annotations: &mcp.ToolAnnotations{
1507+
Title: t("TOOL_GET_RELEASE_BY_TAG_USER_TITLE", "Get a release by tag name"),
1508+
ReadOnlyHint: true,
15791509
},
1580-
Required: []string{"owner", "repo", "tag"},
1510+
InputSchema: GetReleaseByTagInput{}.MCPSchema(),
15811511
},
1582-
}
1583-
1584-
handler := mcp.ToolHandlerFor[map[string]any, any](func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
1585-
owner, err := RequiredParam[string](args, "owner")
1586-
if err != nil {
1587-
return utils.NewToolResultError(err.Error()), nil, nil
1588-
}
1589-
repo, err := RequiredParam[string](args, "repo")
1590-
if err != nil {
1591-
return utils.NewToolResultError(err.Error()), nil, nil
1592-
}
1593-
tag, err := RequiredParam[string](args, "tag")
1594-
if err != nil {
1595-
return utils.NewToolResultError(err.Error()), nil, nil
1596-
}
1512+
func(ctx context.Context, _ *mcp.CallToolRequest, input GetReleaseByTagInput) (*mcp.CallToolResult, any, error) {
1513+
client, err := getClient(ctx)
1514+
if err != nil {
1515+
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1516+
}
15971517

1598-
client, err := getClient(ctx)
1599-
if err != nil {
1600-
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
1601-
}
1518+
release, resp, err := client.Repositories.GetReleaseByTag(ctx, input.Owner, input.Repo, input.Tag)
1519+
if err != nil {
1520+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
1521+
fmt.Sprintf("failed to get release by tag: %s", input.Tag),
1522+
resp,
1523+
err,
1524+
), nil, nil
1525+
}
1526+
defer func() { _ = resp.Body.Close() }()
16021527

1603-
release, resp, err := client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
1604-
if err != nil {
1605-
return ghErrors.NewGitHubAPIErrorResponse(ctx,
1606-
fmt.Sprintf("failed to get release by tag: %s", tag),
1607-
resp,
1608-
err,
1609-
), nil, nil
1610-
}
1611-
defer func() { _ = resp.Body.Close() }()
1528+
if resp.StatusCode != http.StatusOK {
1529+
body, err := io.ReadAll(resp.Body)
1530+
if err != nil {
1531+
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1532+
}
1533+
return utils.NewToolResultError(fmt.Sprintf("failed to get release by tag: %s", string(body))), nil, nil
1534+
}
16121535

1613-
if resp.StatusCode != http.StatusOK {
1614-
body, err := io.ReadAll(resp.Body)
1536+
r, err := json.Marshal(release)
16151537
if err != nil {
1616-
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
1538+
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
16171539
}
1618-
return utils.NewToolResultError(fmt.Sprintf("failed to get release by tag: %s", string(body))), nil, nil
1619-
}
16201540

1621-
r, err := json.Marshal(release)
1622-
if err != nil {
1623-
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
1541+
return utils.NewToolResultText(string(r)), nil, nil
16241542
}
1625-
1626-
return utils.NewToolResultText(string(r)), nil, nil
1627-
})
1628-
1629-
return tool, handler
16301543
}
16311544

16321545
// filterPaths filters the entries in a GitHub tree to find paths that

0 commit comments

Comments
 (0)