Skip to content

Commit 4f73cfd

Browse files
authored
Add repo-scoped support to list_issue_types tool (#2692)
* Add repo-scoped support to list_issue_types tool * Render multi-scope tools as "any of" in generated docs * Clarify issue type field description for repo-scoped list_issue_types
1 parent 6830c4d commit 4f73cfd

10 files changed

Lines changed: 102 additions & 25 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -885,12 +885,13 @@ The following sets of tools are available:
885885
- `state`: New state (string, optional)
886886
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
887887
- `title`: Issue title (string, optional)
888-
- `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
888+
- `type`: Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
889889

890890
- **list_issue_types** - List available issue types
891-
- **Required OAuth Scopes**: `read:org`
892-
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org`
893-
- `owner`: The organization owner of the repository (string, required)
891+
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
892+
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
893+
- `owner`: The account owner of the repository or organization. (string, required)
894+
- `repo`: The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly. (string, optional)
894895

895896
- **list_issues** - List issues
896897
- **Required OAuth Scopes**: `repo`

cmd/github-mcp-server/generate_docs.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,15 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
221221

222222
// OAuth scopes if present
223223
if len(tool.RequiredScopes) > 0 {
224-
fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `"))
224+
// Scope filtering uses "any of" semantics (see scopes.HasRequiredScopes),
225+
// so when multiple required scopes are listed, render them as alternatives
226+
// rather than implying all are required.
227+
scopeList := "`" + strings.Join(tool.RequiredScopes, "`, `") + "`"
228+
if len(tool.RequiredScopes) > 1 {
229+
fmt.Fprintf(buf, " - **Required OAuth Scopes (any of)**: %s\n", scopeList)
230+
} else {
231+
fmt.Fprintf(buf, " - **Required OAuth Scopes**: %s\n", scopeList)
232+
}
225233

226234
// Only show accepted scopes if they differ from required scopes
227235
if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) {

docs/feature-flags.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ runtime behavior (such as output formatting) won't appear here.
7272
- `state`: New state (string, optional)
7373
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
7474
- `title`: Issue title (string, optional)
75-
- `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
75+
- `type`: Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
7676

7777
- **ui_get** - Get UI data
7878
- **Required OAuth Scopes**: `repo`, `read:org`
@@ -116,10 +116,10 @@ runtime behavior (such as output formatting) won't appear here.
116116
- `state`: New state (string, optional)
117117
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
118118
- `title`: Issue title (string, optional)
119-
- `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
119+
- `type`: Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
120120

121121
- **list_issue_fields** - List issue fields
122-
- **Required OAuth Scopes**: `repo`, `read:org`
122+
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
123123
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
124124
- `owner`: The account owner of the repository or organization. The name is not case sensitive. (string, required)
125125
- `repo`: The name of the repository. When provided, returns fields for this specific repository (inherited from its organization). When omitted, returns org-level fields directly. (string, optional)

docs/insiders-features.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The list below is generated from the Go source. It covers tool **inventory and s
6666
- `state`: New state (string, optional)
6767
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
6868
- `title`: Issue title (string, optional)
69-
- `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
69+
- `type`: Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
7070

7171
- **ui_get** - Get UI data
7272
- **Required OAuth Scopes**: `repo`, `read:org`
@@ -110,10 +110,10 @@ The list below is generated from the Go source. It covers tool **inventory and s
110110
- `state`: New state (string, optional)
111111
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
112112
- `title`: Issue title (string, optional)
113-
- `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
113+
- `type`: Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
114114

115115
- **list_issue_fields** - List issue fields
116-
- **Required OAuth Scopes**: `repo`, `read:org`
116+
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
117117
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
118118
- `owner`: The account owner of the repository or organization. The name is not case sensitive. (string, required)
119119
- `repo`: The name of the repository. When provided, returns fields for this specific repository (inherited from its organization). When omitted, returns org-level fields directly. (string, optional)

pkg/errors/error_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package errors
33
import (
44
"context"
55
"fmt"
6-
"net/http"
7-
"testing"
8-
"time"
96
"github.com/google/go-github/v87/github"
107
"github.com/modelcontextprotocol/go-sdk/mcp"
118
"github.com/stretchr/testify/assert"
129
"github.com/stretchr/testify/require"
10+
"net/http"
11+
"testing"
12+
"time"
1313
)
1414

1515
func TestGitHubErrorContext(t *testing.T) {
@@ -687,4 +687,3 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
687687
assert.Contains(t, text, "validation failed")
688688
})
689689
}
690-

pkg/github/__toolsnaps__/issue_write.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"type": "string"
8787
},
8888
"type": {
89-
"description": "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter.",
89+
"description": "Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
9090
"type": "string"
9191
}
9292
},

pkg/github/__toolsnaps__/issue_write_ff_remote_mcp_issue_fields.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
"type": "string"
123123
},
124124
"type": {
125-
"description": "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter.",
125+
"description": "Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
126126
"type": "string"
127127
}
128128
},

pkg/github/__toolsnaps__/list_issue_types.snap

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
"readOnlyHint": true,
44
"title": "List available issue types"
55
},
6-
"description": "List supported issue types for repository owner (organization).",
6+
"description": "List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly.",
77
"inputSchema": {
88
"properties": {
99
"owner": {
10-
"description": "The organization owner of the repository",
10+
"description": "The account owner of the repository or organization.",
11+
"type": "string"
12+
},
13+
"repo": {
14+
"description": "The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.",
1115
"type": "string"
1216
}
1317
},

pkg/github/issues.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,13 +1067,14 @@ func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string,
10671067
return utils.NewToolResultText(string(out)), nil
10681068
}
10691069

1070-
// ListIssueTypes creates a tool to list defined issue types for an organization. This can be used to understand supported issue type values for creating or updating issues.
1070+
// ListIssueTypes creates a tool to list defined issue types for an organization or repository.
1071+
// This can be used to understand supported issue type values for creating or updating issues.
10711072
func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool {
10721073
return NewTool(
10731074
ToolsetMetadataIssues,
10741075
mcp.Tool{
10751076
Name: "list_issue_types",
1076-
Description: t("TOOL_LIST_ISSUE_TYPES_FOR_ORG", "List supported issue types for repository owner (organization)."),
1077+
Description: t("TOOL_LIST_ISSUE_TYPES_FOR_ORG", "List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly."),
10771078
Annotations: &mcp.ToolAnnotations{
10781079
Title: t("TOOL_LIST_ISSUE_TYPES_USER_TITLE", "List available issue types"),
10791080
ReadOnlyHint: true,
@@ -1083,23 +1084,63 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool {
10831084
Properties: map[string]*jsonschema.Schema{
10841085
"owner": {
10851086
Type: "string",
1086-
Description: "The organization owner of the repository",
1087+
Description: "The account owner of the repository or organization.",
1088+
},
1089+
"repo": {
1090+
Type: "string",
1091+
Description: "The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.",
10871092
},
10881093
},
10891094
Required: []string{"owner"},
10901095
},
10911096
},
1092-
[]scopes.Scope{scopes.ReadOrg},
1097+
[]scopes.Scope{scopes.Repo, scopes.ReadOrg},
10931098
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
10941099
owner, err := RequiredParam[string](args, "owner")
10951100
if err != nil {
10961101
return utils.NewToolResultError(err.Error()), nil, nil
10971102
}
1103+
repo, err := OptionalParam[string](args, "repo")
1104+
if err != nil {
1105+
return utils.NewToolResultError(err.Error()), nil, nil
1106+
}
10981107

10991108
client, err := deps.GetClient(ctx)
11001109
if err != nil {
11011110
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
11021111
}
1112+
1113+
if repo != "" {
1114+
apiURL := fmt.Sprintf("repos/%s/%s/issue-types", owner, repo)
1115+
req, err := client.NewRequest(ctx, "GET", apiURL, nil)
1116+
if err != nil {
1117+
return utils.NewToolResultErrorFromErr("failed to create request", err), nil, nil
1118+
}
1119+
var issueTypes []*github.IssueType
1120+
resp, err := client.Do(req, &issueTypes)
1121+
if err != nil {
1122+
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list issue types", resp, err), nil, nil
1123+
}
1124+
defer func() { _ = resp.Body.Close() }()
1125+
1126+
if resp.StatusCode != http.StatusOK {
1127+
body, err := io.ReadAll(resp.Body)
1128+
if err != nil {
1129+
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
1130+
}
1131+
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list issue types", resp, body), nil, nil
1132+
}
1133+
1134+
r, err := json.Marshal(issueTypes)
1135+
if err != nil {
1136+
return utils.NewToolResultErrorFromErr("failed to marshal issue types", err), nil, nil
1137+
}
1138+
1139+
result := utils.NewToolResultText(string(r))
1140+
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
1141+
return result, nil, nil
1142+
}
1143+
11031144
issueTypes, resp, err := client.Organizations.ListIssueTypes(ctx, owner)
11041145
if err != nil {
11051146
return utils.NewToolResultErrorFromErr("failed to list issue types", err), nil, nil
@@ -1890,7 +1931,7 @@ Options are:
18901931
},
18911932
"type": {
18921933
Type: "string",
1893-
Description: "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter.",
1934+
Description: "Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
18941935
},
18951936
"state": {
18961937
Type: "string",
@@ -2182,7 +2223,7 @@ Options are:
21822223
},
21832224
"type": {
21842225
Type: "string",
2185-
Description: "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter.",
2226+
Description: "Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
21862227
},
21872228
"state": {
21882229
Type: "string",

pkg/github/issues_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4812,6 +4812,30 @@ func Test_ListIssueTypes(t *testing.T) {
48124812
expectError: false, // This should be handled by parameter validation, error returned in result
48134813
expectedErrMsg: "missing required parameter: owner",
48144814
},
4815+
{
4816+
name: "successful repo issue types retrieval",
4817+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
4818+
"GET /repos/testorg/testrepo/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes),
4819+
}),
4820+
requestArgs: map[string]any{
4821+
"owner": "testorg",
4822+
"repo": "testrepo",
4823+
},
4824+
expectError: false,
4825+
expectedIssueTypes: mockIssueTypes,
4826+
},
4827+
{
4828+
name: "repo not found",
4829+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
4830+
"GET /repos/testorg/nonexistent/issue-types": mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
4831+
}),
4832+
requestArgs: map[string]any{
4833+
"owner": "testorg",
4834+
"repo": "nonexistent",
4835+
},
4836+
expectError: true,
4837+
expectedErrMsg: "failed to list issue types",
4838+
},
48154839
}
48164840

48174841
for _, tc := range tests {

0 commit comments

Comments
 (0)