Skip to content
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,8 @@ The following sets of tools are available:
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- `repo`: Repository name (string, required)
- `sha`: Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA. (string, optional)
- `since`: Only show commits after this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ) (string, optional)
- `until`: Only show commits before this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ) (string, optional)

- **list_releases** - List releases
- **Required OAuth Scopes**: `repo`
Expand Down
8 changes: 8 additions & 0 deletions pkg/github/__toolsnaps__/list_commits.snap
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
"sha": {
"description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA.",
"type": "string"
},
"since": {
"description": "Only show commits after this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)",
"type": "string"
},
"until": {
"description": "Only show commits before this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)",
"type": "string"
}
},
"required": [
Expand Down
32 changes: 32 additions & 0 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"strings"
"time"

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/inventory"
Expand Down Expand Up @@ -147,6 +148,14 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
Type: "string",
Description: "Author username or email address to filter commits by",
},
"since": {
Type: "string",
Description: "Only show commits after this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)",
},
"until": {
Type: "string",
Description: "Only show commits before this date (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)",
},
},
Required: []string{"owner", "repo"},
}),
Expand All @@ -169,6 +178,27 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
sinceStr, err := OptionalParam[string](args, "since")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
untilStr, err := OptionalParam[string](args, "until")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
var sinceTime, untilTime time.Time
if sinceStr != "" {
sinceTime, err = time.Parse(time.RFC3339, sinceStr)
if err != nil {
return utils.NewToolResultError(fmt.Sprintf("invalid 'since' date format, expected ISO 8601 (YYYY-MM-DDTHH:MM:SSZ): %s", err.Error())), nil, nil
}
}
if untilStr != "" {
untilTime, err = time.Parse(time.RFC3339, untilStr)
if err != nil {
return utils.NewToolResultError(fmt.Sprintf("invalid 'until' date format, expected ISO 8601 (YYYY-MM-DDTHH:MM:SSZ): %s", err.Error())), nil, nil
}
}
pagination, err := OptionalPaginationParams(args)
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
Expand All @@ -181,6 +211,8 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
opts := &github.CommitsListOptions{
SHA: sha,
Author: author,
Since: sinceTime,
Until: untilTime,
ListOptions: github.ListOptions{
Page: pagination.Page,
PerPage: perPage,
Expand Down
45 changes: 45 additions & 0 deletions pkg/github/repositories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,8 @@ func Test_ListCommits(t *testing.T) {
assert.Contains(t, schema.Properties, "repo")
assert.Contains(t, schema.Properties, "sha")
assert.Contains(t, schema.Properties, "author")
assert.Contains(t, schema.Properties, "since")
assert.Contains(t, schema.Properties, "until")
assert.Contains(t, schema.Properties, "page")
assert.Contains(t, schema.Properties, "perPage")
assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})
Expand Down Expand Up @@ -965,6 +967,49 @@ func Test_ListCommits(t *testing.T) {
expectError: false,
expectedCommits: mockCommits,
},
{
name: "successful commits fetch with since and until",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
"since": "2025-01-01T00:00:00Z",
"until": "2025-01-31T23:59:59Z",
"page": "1",
"per_page": "30",
}).andThen(
mockResponse(t, http.StatusOK, mockCommits),
),
}),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"since": "2025-01-01T00:00:00Z",
"until": "2025-01-31T23:59:59Z",
},
expectError: false,
expectedCommits: mockCommits,
},
{
name: "invalid since date format",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"since": "not-a-date",
},
expectError: true,
expectedErrMsg: "invalid 'since' date format",
},
{
name: "invalid until date format",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"until": "2025/01/01",
},
expectError: true,
expectedErrMsg: "invalid 'until' date format",
},
{
name: "commits fetch fails",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
Expand Down