From 51ba135f03b049d8a995a0edc0662dfb4511e875 Mon Sep 17 00:00:00 2001 From: Lucas Bustamante Date: Tue, 24 Mar 2026 15:48:44 -0300 Subject: [PATCH 1/2] feat: add path, since, and until filters to list_commits Add three optional parameters to the list_commits tool that map to existing GitHub API query parameters: - path: filter commits to those touching a specific file or directory - since: only commits after this date (ISO 8601) - until: only commits before this date (ISO 8601) These parameters are already supported by the go-github library's CommitsListOptions struct but were not exposed by the MCP tool. The path filter is particularly useful for monorepo workflows where commit history needs to be scoped to a specific project subdirectory. Time parsing uses the existing parseISOTimestamp helper (shared with list_issues and list_gists) which accepts both YYYY-MM-DDTHH:MM:SSZ and YYYY-MM-DD formats. Closes #197 --- pkg/github/__toolsnaps__/list_commits.snap | 12 ++++ pkg/github/repositories.go | 39 +++++++++++ pkg/github/repositories_test.go | 77 ++++++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/pkg/github/__toolsnaps__/list_commits.snap b/pkg/github/__toolsnaps__/list_commits.snap index 38b63736f..1a773f217 100644 --- a/pkg/github/__toolsnaps__/list_commits.snap +++ b/pkg/github/__toolsnaps__/list_commits.snap @@ -19,6 +19,10 @@ "minimum": 1, "type": "number" }, + "path": { + "description": "Only commits containing this file path will be returned", + "type": "string" + }, "perPage": { "description": "Results per page for pagination (min 1, max 100)", "maximum": 100, @@ -32,6 +36,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 commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)", + "type": "string" + }, + "until": { + "description": "Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)", + "type": "string" } }, "required": [ diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 9376ddad4..305189650 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -147,6 +147,18 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { Type: "string", Description: "Author username or email address to filter commits by", }, + "path": { + Type: "string", + Description: "Only commits containing this file path will be returned", + }, + "since": { + Type: "string", + Description: "Only commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)", + }, + "until": { + Type: "string", + Description: "Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)", + }, }, Required: []string{"owner", "repo"}, }), @@ -169,6 +181,18 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } + path, err := OptionalParam[string](args, "path") + 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 + } pagination, err := OptionalPaginationParams(args) if err != nil { return utils.NewToolResultError(err.Error()), nil, nil @@ -180,12 +204,27 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { } opts := &github.CommitsListOptions{ SHA: sha, + Path: path, Author: author, ListOptions: github.ListOptions{ Page: pagination.Page, PerPage: perPage, }, } + if sinceStr != "" { + sinceTime, err := parseISOTimestamp(sinceStr) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("invalid since timestamp: %s", err)), nil, nil + } + opts.Since = sinceTime + } + if untilStr != "" { + untilTime, err := parseISOTimestamp(untilStr) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("invalid until timestamp: %s", err)), nil, nil + } + opts.Until = untilTime + } client, err := deps.GetClient(ctx) if err != nil { diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index ae2ece0f6..d7bb48738 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -900,6 +900,9 @@ 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, "path") + 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"}) @@ -1020,6 +1023,80 @@ func Test_ListCommits(t *testing.T) { expectError: false, expectedCommits: mockCommits, }, + { + name: "successful commits fetch with path filter", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "path": "src/main.go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "path": "src/main.go", + }, + 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": "2023-01-01T00:00:00Z", + "until": "2023-12-31T23:59:59Z", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "since": "2023-01-01T00:00:00Z", + "until": "2023-12-31T23:59:59Z", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "successful commits fetch with path, since, and author", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "path": "projects/plugins/boost", + "since": "2023-06-15T00:00:00Z", + "author": "username", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "path": "projects/plugins/boost", + "since": "2023-06-15T00:00:00Z", + "author": "username", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "invalid since timestamp returns error", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "since": "not-a-date", + }, + expectError: true, + expectedErrMsg: "invalid since timestamp", + }, { name: "successful commits fetch with pagination", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ From 4b8dbbfdec81c13f05b1f44bf70614d59746b733 Mon Sep 17 00:00:00 2001 From: Lucas Bustamante Date: Tue, 24 Mar 2026 16:00:43 -0300 Subject: [PATCH 2/2] docs: regenerate README with new list_commits parameters --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e9992694e..419f89297 100644 --- a/README.md +++ b/README.md @@ -1242,9 +1242,12 @@ The following sets of tools are available: - `author`: Author username or email address to filter commits by (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) + - `path`: Only commits containing this file path will be returned (string, optional) - `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 commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD) (string, optional) + - `until`: Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD) (string, optional) - **list_releases** - List releases - **Required OAuth Scopes**: `repo`