Skip to content

Commit c74eb7a

Browse files
fix(repos): return typed projected commit lists
Use an optional-field commit DTO for full and projected modern outputs while retaining exact legacy text. Cover SHA and nested commit projections, empty lists, and output schema conformance. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 7d8b518 commit c74eb7a

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

‎pkg/github/repositories.go‎

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,41 @@ type ListCommitsInput struct {
155155
PerPage *int `json:"perPage,omitempty"`
156156
}
157157

158+
type ListCommitOutput struct {
159+
SHA *string `json:"sha,omitempty"`
160+
HTMLURL *string `json:"html_url,omitempty"`
161+
Commit *MinimalCommitInfo `json:"commit,omitempty"`
162+
Author *MinimalUser `json:"author,omitempty"`
163+
Committer *MinimalUser `json:"committer,omitempty"`
164+
}
165+
166+
func structuredListCommitsOutput(commits []MinimalCommit, fields []string) []ListCommitOutput {
167+
output := make([]ListCommitOutput, 0, len(commits))
168+
selected := func(field string) bool {
169+
return len(fields) == 0 || slices.Contains(fields, field)
170+
}
171+
for _, commit := range commits {
172+
item := ListCommitOutput{}
173+
if selected("sha") {
174+
item.SHA = new(commit.SHA)
175+
}
176+
if selected("html_url") {
177+
item.HTMLURL = new(commit.HTMLURL)
178+
}
179+
if selected("commit") {
180+
item.Commit = commit.Commit
181+
}
182+
if selected("author") {
183+
item.Author = commit.Author
184+
}
185+
if selected("committer") {
186+
item.Committer = commit.Committer
187+
}
188+
output = append(output, item)
189+
}
190+
return output
191+
}
192+
158193
func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
159194
schema := &jsonschema.Schema{
160195
Type: "object",
@@ -196,7 +231,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
196231
)
197232
WithPagination(schema)
198233

199-
return NewTool[ListCommitsInput, []MinimalCommit](
234+
return NewTool[ListCommitsInput, []ListCommitOutput](
200235
ToolsetMetadataRepos,
201236
mcp.Tool{
202237
Name: "list_commits",
@@ -208,7 +243,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
208243
InputSchema: schema,
209244
},
210245
scopes.PublicRead(scopes.Repo),
211-
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input ListCommitsInput) (*mcp.CallToolResult, []MinimalCommit, error) {
246+
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input ListCommitsInput) (*mcp.CallToolResult, []ListCommitOutput, error) {
212247
if input.Owner == "" {
213248
return utils.NewToolResultError("missing required parameter: owner"), nil, nil
214249
}
@@ -302,10 +337,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
302337
// follows the same public-untrusted / private-trusted rule as file
303338
// contents. Confidentiality follows repo visibility.
304339
result = attachRepoVisibilityIFCLabel(ctx, deps, client, input.Owner, input.Repo, result, ifc.LabelCommitContents)
305-
if len(input.Fields) > 0 {
306-
return result, nil, nil
307-
}
308-
return result, minimalCommits, nil
340+
return result, structuredListCommitsOutput(minimalCommits, input.Fields), nil
309341
},
310342
normalizeTypedReadArguments(nil, false),
311343
)

‎pkg/github/typed_repository_commit_outputs_test.go‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ func TestTypedRepositoryCommitOutputs(t *testing.T) {
8989
GetReposCommitsByOwnerByRepo: func(w http.ResponseWriter, r *http.Request) {
9090
assert.Equal(t, "4", r.URL.Query().Get("page"))
9191
assert.Equal(t, "9", r.URL.Query().Get("per_page"))
92+
if r.URL.Query().Get("sha") == "empty" {
93+
mockResponse(t, http.StatusOK, []*github.RepositoryCommit{})(w, r)
94+
return
95+
}
9296
mockResponse(t, http.StatusOK, []*github.RepositoryCommit{commit})(w, r)
9397
},
9498
}
@@ -137,6 +141,26 @@ func TestTypedRepositoryCommitOutputs(t *testing.T) {
137141
args: map[string]any{"owner": "owner", "repo": "repo", "page": "4", "perPage": "9"},
138142
text: expectedList,
139143
},
144+
{
145+
name: "list_commits",
146+
args: map[string]any{"owner": "owner", "repo": "repo", "page": "4", "perPage": "9", "fields": []any{"sha"}},
147+
text: `[{"sha":"abc123"}]`,
148+
},
149+
{
150+
name: "list_commits",
151+
args: map[string]any{"owner": "owner", "repo": "repo", "page": "4", "perPage": "9", "fields": []any{"commit"}},
152+
text: `[{"commit":{"message":"A commit"}}]`,
153+
},
154+
{
155+
name: "list_commits",
156+
args: map[string]any{"owner": "owner", "repo": "repo", "page": "4", "perPage": "9", "sha": "empty"},
157+
text: `[]`,
158+
},
159+
{
160+
name: "list_commits",
161+
args: map[string]any{"owner": "owner", "repo": "repo", "page": "4", "perPage": "9", "sha": "empty", "fields": []any{"sha", "commit"}},
162+
text: `[]`,
163+
},
140164
}
141165
for _, call := range calls {
142166
result, err := session.CallTool(context.Background(), &mcp.CallToolParams{Name: call.name, Arguments: call.args})
@@ -172,7 +196,12 @@ func TestTypedRepositoryCommitOutputs(t *testing.T) {
172196
require.NoError(t, err)
173197
require.False(t, filtered.IsError, filtered)
174198
assert.Equal(t, `[{"sha":"abc123"}]`, getTextResult(t, filtered).Text)
175-
assert.Nil(t, filtered.StructuredContent, "field projection keeps the legacy wire shape")
199+
if protocolVersion == "2025-11-25" {
200+
assert.Nil(t, filtered.StructuredContent)
201+
} else {
202+
require.NotNil(t, filtered.StructuredContent)
203+
assert.JSONEq(t, `[{"sha":"abc123"}]`, mustMarshalJSON(t, filtered.StructuredContent))
204+
}
176205
})
177206
}
178207
}

0 commit comments

Comments
 (0)