Skip to content

Commit a3d0961

Browse files
authored
Merge branch 'main' into add-issue-dependencies
2 parents aafd3be + 5259513 commit a3d0961

9 files changed

Lines changed: 396 additions & 27 deletions

File tree

pkg/github/__toolsnaps__/actions_run_trigger.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"properties": {
99
"inputs": {
1010
"description": "Inputs the workflow accepts. Only used for 'run_workflow' method.",
11+
"properties": {},
1112
"type": "object"
1213
},
1314
"method": {

pkg/github/actions.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
544544
"inputs": {
545545
Type: "object",
546546
Description: "Inputs the workflow accepts. Only used for 'run_workflow' method.",
547+
Properties: map[string]*jsonschema.Schema{},
547548
},
548549
"run_id": {
549550
Type: "number",
@@ -574,11 +575,9 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
574575
runID, _ := OptionalIntParam(args, "run_id")
575576

576577
// Get optional inputs parameter
577-
var inputs map[string]any
578-
if requestInputs, ok := args["inputs"]; ok {
579-
if inputsMap, ok := requestInputs.(map[string]any); ok {
580-
inputs = inputsMap
581-
}
578+
inputs, err := OptionalParam[map[string]any](args, "inputs")
579+
if err != nil {
580+
return utils.NewToolResultError(err.Error()), nil, nil
582581
}
583582

584583
// Validate required parameters based on action type

pkg/github/actions_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,37 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) {
377377
expectError: true,
378378
expectedErrMsg: "ref is required for run_workflow action",
379379
},
380+
{
381+
name: "successful workflow run with inputs",
382+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
383+
PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
384+
w.WriteHeader(http.StatusNoContent)
385+
}),
386+
}),
387+
requestArgs: map[string]any{
388+
"method": "run_workflow",
389+
"owner": "owner",
390+
"repo": "repo",
391+
"workflow_id": "12345",
392+
"ref": "main",
393+
"inputs": map[string]any{"FIELD1": "value1", "FIELD2": "value2"},
394+
},
395+
expectError: false,
396+
},
397+
{
398+
name: "invalid inputs type returns error",
399+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
400+
requestArgs: map[string]any{
401+
"method": "run_workflow",
402+
"owner": "owner",
403+
"repo": "repo",
404+
"workflow_id": "12345",
405+
"ref": "main",
406+
"inputs": "not a map",
407+
},
408+
expectError: true,
409+
expectedErrMsg: "parameter inputs is not of type map[string]interface {}, is string",
410+
},
380411
}
381412

382413
for _, tc := range tests {

pkg/github/helper_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
GetReposByOwnerByRepo = "GET /repos/{owner}/{repo}"
3232
GetReposBranchesByOwnerByRepo = "GET /repos/{owner}/{repo}/branches"
3333
GetReposTagsByOwnerByRepo = "GET /repos/{owner}/{repo}/tags"
34+
GetReposCollaboratorsByOwnerByRepo = "GET /repos/{owner}/{repo}/collaborators"
3435
GetReposCommitsByOwnerByRepo = "GET /repos/{owner}/{repo}/commits"
3536
GetReposCommitsByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/commits/{ref}"
3637
GetReposContentsByOwnerByRepoByPath = "GET /repos/{owner}/{repo}/contents/{path}"

pkg/github/issues.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
ghErrors "github.com/github/github-mcp-server/pkg/errors"
13+
"github.com/github/github-mcp-server/pkg/ifc"
1314
"github.com/github/github-mcp-server/pkg/inventory"
1415
"github.com/github/github-mcp-server/pkg/sanitize"
1516
"github.com/github/github-mcp-server/pkg/scopes"
@@ -130,6 +131,7 @@ type IssueFragment struct {
130131
// Common interface for all issue query types
131132
type IssueQueryResult interface {
132133
GetIssueFragment() IssueQueryFragment
134+
GetIsPrivate() bool
133135
}
134136

135137
type IssueQueryFragment struct {
@@ -146,28 +148,32 @@ type IssueQueryFragment struct {
146148
// ListIssuesQuery is the root query structure for fetching issues with optional label filtering.
147149
type ListIssuesQuery struct {
148150
Repository struct {
149-
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
151+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
152+
IsPrivate githubv4.Boolean
150153
} `graphql:"repository(owner: $owner, name: $repo)"`
151154
}
152155

153156
// ListIssuesQueryTypeWithLabels is the query structure for fetching issues with optional label filtering.
154157
type ListIssuesQueryTypeWithLabels struct {
155158
Repository struct {
156-
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
159+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
160+
IsPrivate githubv4.Boolean
157161
} `graphql:"repository(owner: $owner, name: $repo)"`
158162
}
159163

160164
// ListIssuesQueryWithSince is the query structure for fetching issues without label filtering but with since filtering.
161165
type ListIssuesQueryWithSince struct {
162166
Repository struct {
163-
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
167+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
168+
IsPrivate githubv4.Boolean
164169
} `graphql:"repository(owner: $owner, name: $repo)"`
165170
}
166171

167172
// ListIssuesQueryTypeWithLabelsWithSince is the query structure for fetching issues with both label and since filtering.
168173
type ListIssuesQueryTypeWithLabelsWithSince struct {
169174
Repository struct {
170-
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
175+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
176+
IsPrivate githubv4.Boolean
171177
} `graphql:"repository(owner: $owner, name: $repo)"`
172178
}
173179

@@ -176,18 +182,28 @@ func (q *ListIssuesQueryTypeWithLabels) GetIssueFragment() IssueQueryFragment {
176182
return q.Repository.Issues
177183
}
178184

185+
func (q *ListIssuesQueryTypeWithLabels) GetIsPrivate() bool { return bool(q.Repository.IsPrivate) }
186+
179187
func (q *ListIssuesQuery) GetIssueFragment() IssueQueryFragment {
180188
return q.Repository.Issues
181189
}
182190

191+
func (q *ListIssuesQuery) GetIsPrivate() bool { return bool(q.Repository.IsPrivate) }
192+
183193
func (q *ListIssuesQueryWithSince) GetIssueFragment() IssueQueryFragment {
184194
return q.Repository.Issues
185195
}
186196

197+
func (q *ListIssuesQueryWithSince) GetIsPrivate() bool { return bool(q.Repository.IsPrivate) }
198+
187199
func (q *ListIssuesQueryTypeWithLabelsWithSince) GetIssueFragment() IssueQueryFragment {
188200
return q.Repository.Issues
189201
}
190202

203+
func (q *ListIssuesQueryTypeWithLabelsWithSince) GetIsPrivate() bool {
204+
return bool(q.Repository.IsPrivate)
205+
}
206+
191207
func getIssueQueryType(hasLabels bool, hasSince bool) any {
192208
switch {
193209
case hasLabels && hasSince:
@@ -1846,11 +1862,35 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
18461862
}
18471863

18481864
var resp MinimalIssuesResponse
1865+
var isPrivate bool
18491866
if queryResult, ok := issueQuery.(IssueQueryResult); ok {
18501867
resp = convertToMinimalIssuesResponse(queryResult.GetIssueFragment())
1868+
isPrivate = queryResult.GetIsPrivate()
18511869
}
18521870

1853-
return MarshalledTextResult(resp), nil, nil
1871+
result := MarshalledTextResult(resp)
1872+
if deps.GetFlags(ctx).InsidersMode {
1873+
if result.Meta == nil {
1874+
result.Meta = mcp.Meta{}
1875+
}
1876+
var readers []string
1877+
if isPrivate {
1878+
restClient, err := deps.GetClient(ctx)
1879+
if err == nil {
1880+
if collaborators, err := FetchRepoCollaborators(ctx, restClient, owner, repo); err == nil {
1881+
readers = collaborators
1882+
}
1883+
}
1884+
// Fall back to the repository owner so the reader set is
1885+
// never empty for a private repository even if the
1886+
// collaborators lookup fails.
1887+
if len(readers) == 0 {
1888+
readers = []string{owner}
1889+
}
1890+
}
1891+
result.Meta["ifc"] = ifc.LabelListIssues(isPrivate, readers)
1892+
}
1893+
return result, nil, nil
18541894
})
18551895
}
18561896

0 commit comments

Comments
 (0)