From 97f1b83e733d71be62e0efbac4ae20970a39d690 Mon Sep 17 00:00:00 2001 From: Rene Martin Rodriguez Date: Mon, 28 Jul 2025 16:59:13 +0100 Subject: [PATCH 1/2] - Updated ListCommits function to accept 'since' and 'until' date parameters for filtering commits. - Enhanced error handling for invalid date formats. - Added corresponding test cases to validate new functionality and ensure proper behavior with various input scenarios. --- pkg/github/repositories.go | 34 +++++++++++ pkg/github/repositories_test.go | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index ecd36d7e0..3d0b974d1 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "strings" + "time" ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/raw" @@ -115,6 +116,12 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t mcp.WithString("author", mcp.Description("Author username or email address to filter commits by"), ), + mcp.WithString("since", + mcp.Description("Only return commits after this date (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')"), + ), + mcp.WithString("until", + mcp.Description("Only return commits before this date (ISO 8601 format, e.g., '2024-12-31T23:59:59Z')"), + ), WithPagination(), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -134,6 +141,14 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t if err != nil { return mcp.NewToolResultError(err.Error()), nil } + since, err := OptionalParam[string](request, "since") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + until, err := OptionalParam[string](request, "until") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil @@ -143,6 +158,7 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t if perPage == 0 { perPage = 30 } + opts := &github.CommitsListOptions{ SHA: sha, Author: author, @@ -152,6 +168,24 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t }, } + // Parse since time if provided + if since != "" { + sinceTime, err := time.Parse(time.RFC3339, since) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid since date format (use ISO 8601/RFC3339): %s", err.Error())), nil + } + opts.Since = sinceTime + } + + // Parse until time if provided + if until != "" { + untilTime, err := time.Parse(time.RFC3339, until) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid until date format (use ISO 8601/RFC3339): %s", err.Error())), nil + } + opts.Until = untilTime + } + client, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 1572a12f4..82944b496 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -719,6 +719,8 @@ func Test_ListCommits(t *testing.T) { assert.Contains(t, tool.InputSchema.Properties, "repo") assert.Contains(t, tool.InputSchema.Properties, "sha") assert.Contains(t, tool.InputSchema.Properties, "author") + assert.Contains(t, tool.InputSchema.Properties, "since") + assert.Contains(t, tool.InputSchema.Properties, "until") assert.Contains(t, tool.InputSchema.Properties, "page") assert.Contains(t, tool.InputSchema.Properties, "perPage") assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"}) @@ -826,6 +828,100 @@ func Test_ListCommits(t *testing.T) { expectError: false, expectedCommits: mockCommits, }, + { + name: "successful commits fetch with time filtering", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposCommitsByOwnerByRepo, + expectQueryParams(t, map[string]string{ + "since": "2024-01-01T00:00:00Z", + "until": "2024-12-31T23:59:59Z", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "since": "2024-01-01T00:00:00Z", + "until": "2024-12-31T23:59:59Z", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "successful commits fetch with only since parameter", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposCommitsByOwnerByRepo, + expectQueryParams(t, map[string]string{ + "since": "2024-06-01T00:00:00Z", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "since": "2024-06-01T00:00:00Z", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "successful commits fetch with only until parameter", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposCommitsByOwnerByRepo, + expectQueryParams(t, map[string]string{ + "until": "2024-06-30T23:59:59Z", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "until": "2024-06-30T23:59:59Z", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "invalid since date format", + mockedClient: mock.NewMockedHTTPClient( + // No HTTP requests expected due to early validation error + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "since": "invalid-date-format", + }, + expectError: false, // This returns a tool error, not a Go error + expectedErrMsg: "invalid since date format", + }, + { + name: "invalid until date format", + mockedClient: mock.NewMockedHTTPClient( + // No HTTP requests expected due to early validation error + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "until": "2024/12/31", // Wrong format + }, + expectError: false, // This returns a tool error, not a Go error + expectedErrMsg: "invalid until date format", + }, { name: "commits fetch fails", mockedClient: mock.NewMockedHTTPClient( @@ -867,6 +963,14 @@ func Test_ListCommits(t *testing.T) { return } + if tc.expectedErrMsg != "" { + require.NoError(t, err) + require.True(t, result.IsError) + errorContent := getErrorResult(t, result) + assert.Contains(t, errorContent.Text, tc.expectedErrMsg) + return + } + require.NoError(t, err) require.False(t, result.IsError) From 76e356ce1b991795ac5ab56d6330bd2f47b781a6 Mon Sep 17 00:00:00 2001 From: Rene Martin Rodriguez Date: Mon, 28 Jul 2025 19:01:46 +0100 Subject: [PATCH 2/2] Adding Path support too to the tool --- pkg/github/repositories.go | 10 ++++++- pkg/github/repositories_test.go | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 3d0b974d1..6a90787a9 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -97,7 +97,7 @@ func GetCommit(getClient GetClientFn, t translations.TranslationHelperFunc) (too // ListCommits creates a tool to get commits of a branch in a repository. func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_commits", - mcp.WithDescription(t("TOOL_LIST_COMMITS_DESCRIPTION", "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).")), + mcp.WithDescription(t("TOOL_LIST_COMMITS_DESCRIPTION", "Get list of commits of a branch in a GitHub repository. Can be filtered by author, date range (since/until), or file path. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).")), mcp.WithToolAnnotation(mcp.ToolAnnotation{ Title: t("TOOL_LIST_COMMITS_USER_TITLE", "List commits"), ReadOnlyHint: ToBoolPtr(true), @@ -122,6 +122,9 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t mcp.WithString("until", mcp.Description("Only return commits before this date (ISO 8601 format, e.g., '2024-12-31T23:59:59Z')"), ), + mcp.WithString("path", + mcp.Description("Only return commits that touch the specified file path"), + ), WithPagination(), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -149,6 +152,10 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t if err != nil { return mcp.NewToolResultError(err.Error()), nil } + path, err := OptionalParam[string](request, "path") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil @@ -162,6 +169,7 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t opts := &github.CommitsListOptions{ SHA: sha, Author: author, + Path: path, ListOptions: github.ListOptions{ Page: pagination.Page, PerPage: perPage, diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 82944b496..cd7098cc2 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -721,6 +721,7 @@ func Test_ListCommits(t *testing.T) { assert.Contains(t, tool.InputSchema.Properties, "author") assert.Contains(t, tool.InputSchema.Properties, "since") assert.Contains(t, tool.InputSchema.Properties, "until") + assert.Contains(t, tool.InputSchema.Properties, "path") assert.Contains(t, tool.InputSchema.Properties, "page") assert.Contains(t, tool.InputSchema.Properties, "perPage") assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"}) @@ -922,6 +923,56 @@ func Test_ListCommits(t *testing.T) { expectError: false, // This returns a tool error, not a Go error expectedErrMsg: "invalid until date format", }, + { + name: "successful commits fetch with path filtering", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposCommitsByOwnerByRepo, + expectQueryParams(t, map[string]string{ + "path": "src/main.go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "path": "src/main.go", + }, + expectError: false, + expectedCommits: mockCommits, + }, + { + name: "successful commits fetch with path and time filtering", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposCommitsByOwnerByRepo, + expectQueryParams(t, map[string]string{ + "path": "docs/README.md", + "since": "2024-01-01T00:00:00Z", + "until": "2024-12-31T23:59:59Z", + "author": "testuser", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "path": "docs/README.md", + "since": "2024-01-01T00:00:00Z", + "until": "2024-12-31T23:59:59Z", + "author": "testuser", + }, + expectError: false, + expectedCommits: mockCommits, + }, { name: "commits fetch fails", mockedClient: mock.NewMockedHTTPClient( pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy