Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,9 +875,10 @@ The following sets of tools are available:
- `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)

- **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**: `repo`, `read:org`
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `repo`, `write:org`
Comment thread
kelsey-myers marked this conversation as resolved.
- `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`
Expand Down
7 changes: 3 additions & 4 deletions pkg/errors/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -687,4 +687,3 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
assert.Contains(t, text, "validation failed")
})
}

8 changes: 6 additions & 2 deletions pkg/github/__toolsnaps__/list_issue_types.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
},
Expand Down
49 changes: 45 additions & 4 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions pkg/github/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4802,6 +4802,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 {
Expand Down
Loading