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
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:
@@ -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`
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user