Add repo-scoped support to list_issue_types tool (#2692)
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled

* 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
This commit is contained in:
Kelsey Myers
2026-06-17 17:04:18 +01:00
committed by GitHub
parent 6830c4d394
commit 4f73cfd1db
10 changed files with 102 additions and 25 deletions
+5 -4
View File
@@ -885,12 +885,13 @@ The following sets of tools are available:
- `state`: New state (string, optional)
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
- `title`: Issue title (string, optional)
- `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)
- `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)
- **list_issue_types** - List available issue types
- **Required OAuth Scopes**: `read:org`
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org`
- `owner`: The organization owner of the repository (string, required)
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
- `owner`: The account owner of the repository or organization. (string, required)
- `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)
- **list_issues** - List issues
- **Required OAuth Scopes**: `repo`
+9 -1
View File
@@ -221,7 +221,15 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
// OAuth scopes if present
if len(tool.RequiredScopes) > 0 {
fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `"))
// Scope filtering uses "any of" semantics (see scopes.HasRequiredScopes),
// so when multiple required scopes are listed, render them as alternatives
// rather than implying all are required.
scopeList := "`" + strings.Join(tool.RequiredScopes, "`, `") + "`"
if len(tool.RequiredScopes) > 1 {
fmt.Fprintf(buf, " - **Required OAuth Scopes (any of)**: %s\n", scopeList)
} else {
fmt.Fprintf(buf, " - **Required OAuth Scopes**: %s\n", scopeList)
}
// Only show accepted scopes if they differ from required scopes
if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) {
+3 -3
View File
@@ -72,7 +72,7 @@ runtime behavior (such as output formatting) won't appear here.
- `state`: New state (string, optional)
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
- `title`: Issue title (string, optional)
- `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)
- `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)
- **ui_get** - Get UI data
- **Required OAuth Scopes**: `repo`, `read:org`
@@ -116,10 +116,10 @@ runtime behavior (such as output formatting) won't appear here.
- `state`: New state (string, optional)
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
- `title`: Issue title (string, optional)
- `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)
- `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)
- **list_issue_fields** - List issue fields
- **Required OAuth Scopes**: `repo`, `read:org`
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
- `owner`: The account owner of the repository or organization. The name is not case sensitive. (string, required)
- `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)
+3 -3
View File
@@ -66,7 +66,7 @@ The list below is generated from the Go source. It covers tool **inventory and s
- `state`: New state (string, optional)
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
- `title`: Issue title (string, optional)
- `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)
- `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)
- **ui_get** - Get UI data
- **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
- `state`: New state (string, optional)
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
- `title`: Issue title (string, optional)
- `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)
- `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)
- **list_issue_fields** - List issue fields
- **Required OAuth Scopes**: `repo`, `read:org`
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
- `owner`: The account owner of the repository or organization. The name is not case sensitive. (string, required)
- `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)
+3 -4
View File
@@ -3,13 +3,13 @@ package errors
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/google/go-github/v87/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"testing"
"time"
)
func TestGitHubErrorContext(t *testing.T) {
@@ -687,4 +687,3 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
assert.Contains(t, text, "validation failed")
})
}
+1 -1
View File
@@ -86,7 +86,7 @@
"type": "string"
},
"type": {
"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.",
"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.",
"type": "string"
}
},
@@ -122,7 +122,7 @@
"type": "string"
},
"type": {
"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.",
"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.",
"type": "string"
}
},
@@ -3,11 +3,15 @@
"readOnlyHint": true,
"title": "List available issue types"
},
"description": "List supported issue types for repository owner (organization).",
"description": "List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly.",
"inputSchema": {
"properties": {
"owner": {
"description": "The organization owner of the repository",
"description": "The account owner of the repository or organization.",
"type": "string"
},
"repo": {
"description": "The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.",
"type": "string"
}
},
+47 -6
View File
@@ -1067,13 +1067,14 @@ func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string,
return utils.NewToolResultText(string(out)), nil
}
// 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.
// ListIssueTypes creates a tool to list defined issue types for an organization or repository.
// This can be used to understand supported issue type values for creating or updating issues.
func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool {
return NewTool(
ToolsetMetadataIssues,
mcp.Tool{
Name: "list_issue_types",
Description: t("TOOL_LIST_ISSUE_TYPES_FOR_ORG", "List supported issue types for repository owner (organization)."),
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."),
Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_LIST_ISSUE_TYPES_USER_TITLE", "List available issue types"),
ReadOnlyHint: true,
@@ -1083,23 +1084,63 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool {
Properties: map[string]*jsonschema.Schema{
"owner": {
Type: "string",
Description: "The organization owner of the repository",
Description: "The account owner of the repository or organization.",
},
"repo": {
Type: "string",
Description: "The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.",
},
},
Required: []string{"owner"},
},
},
[]scopes.Scope{scopes.ReadOrg},
[]scopes.Scope{scopes.Repo, scopes.ReadOrg},
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
owner, err := RequiredParam[string](args, "owner")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
repo, err := OptionalParam[string](args, "repo")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := deps.GetClient(ctx)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
}
if repo != "" {
apiURL := fmt.Sprintf("repos/%s/%s/issue-types", owner, repo)
req, err := client.NewRequest(ctx, "GET", apiURL, nil)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to create request", err), nil, nil
}
var issueTypes []*github.IssueType
resp, err := client.Do(req, &issueTypes)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list issue types", resp, err), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list issue types", resp, body), nil, nil
}
r, err := json.Marshal(issueTypes)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to marshal issue types", err), nil, nil
}
result := utils.NewToolResultText(string(r))
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
return result, nil, nil
}
issueTypes, resp, err := client.Organizations.ListIssueTypes(ctx, owner)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to list issue types", err), nil, nil
@@ -1890,7 +1931,7 @@ Options are:
},
"type": {
Type: "string",
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.",
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.",
},
"state": {
Type: "string",
@@ -2182,7 +2223,7 @@ Options are:
},
"type": {
Type: "string",
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.",
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.",
},
"state": {
Type: "string",
+24
View File
@@ -4812,6 +4812,30 @@ func Test_ListIssueTypes(t *testing.T) {
expectError: false, // This should be handled by parameter validation, error returned in result
expectedErrMsg: "missing required parameter: owner",
},
{
name: "successful repo issue types retrieval",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
"GET /repos/testorg/testrepo/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes),
}),
requestArgs: map[string]any{
"owner": "testorg",
"repo": "testrepo",
},
expectError: false,
expectedIssueTypes: mockIssueTypes,
},
{
name: "repo not found",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
"GET /repos/testorg/nonexistent/issue-types": mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
}),
requestArgs: map[string]any{
"owner": "testorg",
"repo": "nonexistent",
},
expectError: true,
expectedErrMsg: "failed to list issue types",
},
}
for _, tc := range tests {