Skip to content

Commit abc5398

Browse files
authored
Adjust to list_issue_fields
1 parent e2bb101 commit abc5398

6 files changed

Lines changed: 262 additions & 145 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,11 @@ The following sets of tools are available:
874874
- `title`: Issue title (string, optional)
875875
- `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)
876876

877+
- **list_issue_fields** - List repository issue fields
878+
- **Required OAuth Scopes**: `repo`
879+
- `owner`: The account owner of the repository. The name is not case sensitive. (string, required)
880+
- `repo`: The name of the repository. The name is not case sensitive. (string, required)
881+
877882
- **list_issue_types** - List available issue types
878883
- **Required OAuth Scopes**: `read:org`
879884
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org`
@@ -891,11 +896,6 @@ The following sets of tools are available:
891896
- `since`: Filter by date (ISO 8601 timestamp) (string, optional)
892897
- `state`: Filter by state, by default both open and closed issues are returned when not provided (string, optional)
893898

894-
- **list_org_issue_fields** - List organization issue fields
895-
- **Required OAuth Scopes**: `read:org`
896-
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org`
897-
- `org`: The organization name. The name is not case sensitive. (string, required)
898-
899899
- **search_issues** - Search issues
900900
- **Required OAuth Scopes**: `repo`
901901
- `order`: Sort order (string, optional)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"annotations": {
3+
"readOnlyHint": true,
4+
"title": "List repository issue fields"
5+
},
6+
"description": "List issue fields for a repository. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names.",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description": "The account owner of the repository. The name is not case sensitive.",
11+
"type": "string"
12+
},
13+
"repo": {
14+
"description": "The name of the repository. The name is not case sensitive.",
15+
"type": "string"
16+
}
17+
},
18+
"required": [
19+
"owner",
20+
"repo"
21+
],
22+
"type": "object"
23+
},
24+
"name": "list_issue_fields"
25+
}

pkg/github/__toolsnaps__/list_org_issue_fields.snap

Lines changed: 0 additions & 20 deletions
This file was deleted.

pkg/github/issue_fields.go

Lines changed: 113 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,19 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"net/http"
87

9-
ghErrors "github.com/github/github-mcp-server/pkg/errors"
108
"github.com/github/github-mcp-server/pkg/inventory"
119
"github.com/github/github-mcp-server/pkg/scopes"
1210
"github.com/github/github-mcp-server/pkg/translations"
1311
"github.com/github/github-mcp-server/pkg/utils"
1412
"github.com/google/jsonschema-go/jsonschema"
1513
"github.com/modelcontextprotocol/go-sdk/mcp"
14+
"github.com/shurcooL/githubv4"
1615
)
1716

18-
// IssueField represents an organization-level issue field definition.
17+
// IssueField represents a repository issue field definition.
1918
type IssueField struct {
20-
ID int64 `json:"id"`
21-
NodeID string `json:"node_id"`
19+
ID string `json:"id"`
2220
Name string `json:"name"`
2321
Description string `json:"description,omitempty"`
2422
DataType string `json:"data_type"`
@@ -28,68 +26,148 @@ type IssueField struct {
2826

2927
// IssueSingleSelectFieldOption represents an option for a single_select issue field.
3028
type IssueSingleSelectFieldOption struct {
31-
ID int64 `json:"id"`
29+
ID string `json:"id"`
3230
Name string `json:"name"`
3331
Description string `json:"description,omitempty"`
3432
Color string `json:"color"`
35-
Priority int64 `json:"priority"`
33+
Priority *int `json:"priority,omitempty"`
3634
}
3735

38-
// ListOrgIssueFields creates a tool to list issue field definitions for an organization.
39-
func ListOrgIssueFields(t translations.TranslationHelperFunc) inventory.ServerTool {
36+
// issueFieldsQuery is the GraphQL query for listing issue fields on a repository.
37+
type issueFieldsQuery struct {
38+
Repository struct {
39+
IssueFields struct {
40+
Nodes []struct {
41+
TypeName githubv4.String `graphql:"__typename"`
42+
// All field types share these fields; any populated fragment gives the same values.
43+
IssueFieldText struct {
44+
ID githubv4.ID
45+
Name githubv4.String
46+
Description githubv4.String
47+
DataType githubv4.String
48+
Visibility githubv4.String
49+
} `graphql:"... on IssueFieldText"`
50+
IssueFieldNumber struct {
51+
ID githubv4.ID
52+
Name githubv4.String
53+
Description githubv4.String
54+
DataType githubv4.String
55+
Visibility githubv4.String
56+
} `graphql:"... on IssueFieldNumber"`
57+
IssueFieldDate struct {
58+
ID githubv4.ID
59+
Name githubv4.String
60+
Description githubv4.String
61+
DataType githubv4.String
62+
Visibility githubv4.String
63+
} `graphql:"... on IssueFieldDate"`
64+
IssueFieldSingleSelect struct {
65+
ID githubv4.ID
66+
Name githubv4.String
67+
Description githubv4.String
68+
DataType githubv4.String
69+
Visibility githubv4.String
70+
Options []struct {
71+
ID githubv4.ID
72+
Name githubv4.String
73+
Description githubv4.String
74+
Color githubv4.String
75+
Priority *int
76+
}
77+
} `graphql:"... on IssueFieldSingleSelect"`
78+
}
79+
} `graphql:"issueFields(first: 100)"`
80+
} `graphql:"repository(owner: $owner, name: $name)"`
81+
}
82+
83+
// ListIssueFields creates a tool to list issue field definitions for a repository.
84+
func ListIssueFields(t translations.TranslationHelperFunc) inventory.ServerTool {
4085
return NewTool(
4186
ToolsetMetadataIssues,
4287
mcp.Tool{
43-
Name: "list_org_issue_fields",
44-
Description: t("TOOL_LIST_ORG_ISSUE_FIELDS_DESCRIPTION", "List issue fields for an organization. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names."),
88+
Name: "list_issue_fields",
89+
Description: t("TOOL_LIST_ISSUE_FIELDS_DESCRIPTION", "List issue fields for a repository. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names."),
4590
Annotations: &mcp.ToolAnnotations{
46-
Title: t("TOOL_LIST_ORG_ISSUE_FIELDS_USER_TITLE", "List organization issue fields"),
91+
Title: t("TOOL_LIST_ISSUE_FIELDS_USER_TITLE", "List repository issue fields"),
4792
ReadOnlyHint: true,
4893
},
4994
InputSchema: &jsonschema.Schema{
5095
Type: "object",
5196
Properties: map[string]*jsonschema.Schema{
52-
"org": {
97+
"owner": {
5398
Type: "string",
54-
Description: "The organization name. The name is not case sensitive.",
99+
Description: "The account owner of the repository. The name is not case sensitive.",
100+
},
101+
"repo": {
102+
Type: "string",
103+
Description: "The name of the repository. The name is not case sensitive.",
55104
},
56105
},
57-
Required: []string{"org"},
106+
Required: []string{"owner", "repo"},
58107
},
59108
},
60-
[]scopes.Scope{scopes.ReadOrg},
109+
[]scopes.Scope{scopes.Repo},
61110
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
62-
org, err := RequiredParam[string](args, "org")
111+
owner, err := RequiredParam[string](args, "owner")
63112
if err != nil {
64113
return utils.NewToolResultError(err.Error()), nil, nil
65114
}
66-
67-
client, err := deps.GetClient(ctx)
115+
repo, err := RequiredParam[string](args, "repo")
68116
if err != nil {
69-
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
117+
return utils.NewToolResultError(err.Error()), nil, nil
70118
}
71119

72-
reqURL := fmt.Sprintf("orgs/%s/issue-fields", org)
73-
req, err := client.NewRequest(http.MethodGet, reqURL, nil)
120+
gqlClient, err := deps.GetGQLClient(ctx)
74121
if err != nil {
75-
return utils.NewToolResultErrorFromErr("failed to create request", err), nil, nil
122+
return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err), nil, nil
76123
}
77124

78-
var fields []*IssueField
79-
resp, err := client.Do(ctx, req, &fields)
80-
if resp != nil {
81-
defer func() { _ = resp.Body.Close() }()
125+
var query issueFieldsQuery
126+
vars := map[string]any{
127+
"owner": githubv4.String(owner),
128+
"name": githubv4.String(repo),
82129
}
83-
if err != nil {
84-
if resp != nil && resp.StatusCode == http.StatusNotFound {
85-
// Org doesn't have issue fields enabled — return empty list
86-
result, marshalErr := json.Marshal([]*IssueField{})
87-
if marshalErr != nil {
88-
return utils.NewToolResultErrorFromErr("failed to marshal response", marshalErr), nil, nil
130+
if err := gqlClient.Query(ctx, &query, vars); err != nil {
131+
return utils.NewToolResultErrorFromErr("failed to list issue fields", err), nil, nil
132+
}
133+
134+
fields := make([]IssueField, 0, len(query.Repository.IssueFields.Nodes))
135+
for _, node := range query.Repository.IssueFields.Nodes {
136+
var f IssueField
137+
// Use TypeName to discriminate; shurcooL populates all fragment structs with the
138+
// same shared field values, so any non-SingleSelect struct gives the correct data.
139+
switch string(node.TypeName) {
140+
case "IssueFieldSingleSelect":
141+
opts := make([]IssueSingleSelectFieldOption, 0, len(node.IssueFieldSingleSelect.Options))
142+
for _, o := range node.IssueFieldSingleSelect.Options {
143+
opts = append(opts, IssueSingleSelectFieldOption{
144+
ID: fmt.Sprintf("%v", o.ID),
145+
Name: string(o.Name),
146+
Description: string(o.Description),
147+
Color: string(o.Color),
148+
Priority: o.Priority,
149+
})
150+
}
151+
f = IssueField{
152+
ID: fmt.Sprintf("%v", node.IssueFieldSingleSelect.ID),
153+
Name: string(node.IssueFieldSingleSelect.Name),
154+
Description: string(node.IssueFieldSingleSelect.Description),
155+
DataType: string(node.IssueFieldSingleSelect.DataType),
156+
Visibility: string(node.IssueFieldSingleSelect.Visibility),
157+
Options: opts,
158+
}
159+
case "IssueFieldText", "IssueFieldNumber", "IssueFieldDate":
160+
f = IssueField{
161+
ID: fmt.Sprintf("%v", node.IssueFieldText.ID),
162+
Name: string(node.IssueFieldText.Name),
163+
Description: string(node.IssueFieldText.Description),
164+
DataType: string(node.IssueFieldText.DataType),
165+
Visibility: string(node.IssueFieldText.Visibility),
89166
}
90-
return utils.NewToolResultText(string(result)), nil, nil
167+
default:
168+
continue
91169
}
92-
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list issue fields", resp, err), nil, nil
170+
fields = append(fields, f)
93171
}
94172

95173
r, err := json.Marshal(fields)

0 commit comments

Comments
 (0)