Skip to content

Commit c1afb88

Browse files
committed
refactor to map to most suitable query based on user inputs at runtime
1 parent 5d7230d commit c1afb88

File tree

1 file changed

+144
-125
lines changed

1 file changed

+144
-125
lines changed

pkg/github/discussions.go

Lines changed: 144 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"log"
78

89
"github.com/github/github-mcp-server/pkg/translations"
910
"github.com/go-viper/mapstructure/v2"
@@ -13,6 +14,71 @@ import (
1314
"github.com/shurcooL/githubv4"
1415
)
1516

17+
// Define reusable fragments for discussions
18+
type DiscussionFragment struct {
19+
Number githubv4.Int
20+
Title githubv4.String
21+
CreatedAt githubv4.DateTime
22+
UpdatedAt githubv4.DateTime
23+
Author struct {
24+
Login githubv4.String
25+
}
26+
Category struct {
27+
Name githubv4.String
28+
} `graphql:"category"`
29+
URL githubv4.String `graphql:"url"`
30+
}
31+
32+
type discussionQueries struct {
33+
BasicNoOrder struct {
34+
Repository struct {
35+
Discussions struct {
36+
Nodes []DiscussionFragment
37+
} `graphql:"discussions(first: 100)"`
38+
} `graphql:"repository(owner: $owner, name: $repo)"`
39+
}
40+
41+
BasicWithOrder struct {
42+
Repository struct {
43+
Discussions struct {
44+
Nodes []DiscussionFragment
45+
} `graphql:"discussions(first: 100, orderBy: $orderBy)"`
46+
} `graphql:"repository(owner: $owner, name: $repo)"`
47+
}
48+
49+
WithCategoryAndOrder struct {
50+
Repository struct {
51+
Discussions struct {
52+
Nodes []DiscussionFragment
53+
} `graphql:"discussions(first: 100, categoryId: $categoryId, orderBy: $orderBy)"`
54+
} `graphql:"repository(owner: $owner, name: $repo)"`
55+
}
56+
57+
WithCategoryNoOrder struct {
58+
Repository struct {
59+
Discussions struct {
60+
Nodes []DiscussionFragment
61+
} `graphql:"discussions(first: 100, categoryId: $categoryId)"`
62+
} `graphql:"repository(owner: $owner, name: $repo)"`
63+
}
64+
}
65+
66+
func fragmentToDiscussion(fragment DiscussionFragment) *github.Discussion {
67+
return &github.Discussion{
68+
Number: github.Ptr(int(fragment.Number)),
69+
Title: github.Ptr(string(fragment.Title)),
70+
HTMLURL: github.Ptr(string(fragment.URL)),
71+
CreatedAt: &github.Timestamp{Time: fragment.CreatedAt.Time},
72+
UpdatedAt: &github.Timestamp{Time: fragment.UpdatedAt.Time},
73+
User: &github.User{
74+
Login: github.Ptr(string(fragment.Author.Login)),
75+
},
76+
DiscussionCategory: &github.DiscussionCategory{
77+
Name: github.Ptr(string(fragment.Category.Name)),
78+
},
79+
}
80+
}
81+
1682
func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1783
return mcp.NewTool("list_discussions",
1884
mcp.WithDescription(t("TOOL_LIST_DISCUSSIONS_DESCRIPTION", "List discussions for a repository")),
@@ -32,16 +98,15 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
3298
mcp.Description("Optional filter by discussion category ID. If provided, only discussions with this category are listed."),
3399
),
34100
mcp.WithString("orderBy",
35-
mcp.Description("Order discussions by field"),
101+
mcp.Description("Order discussions by field. If provided, the 'direction' also needs to be provided."),
36102
mcp.Enum("CREATED_AT", "UPDATED_AT"),
37103
),
38104
mcp.WithString("direction",
39-
mcp.Description("Order direction"),
105+
mcp.Description("Order direction."),
40106
mcp.Enum("ASC", "DESC"),
41107
),
42108
),
43109
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
44-
// Required params
45110
owner, err := RequiredParam[string](request, "owner")
46111
if err != nil {
47112
return mcp.NewToolResultError(err.Error()), nil
@@ -51,146 +116,100 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
51116
return mcp.NewToolResultError(err.Error()), nil
52117
}
53118

54-
// Optional params
55119
category, err := OptionalParam[string](request, "category")
56120
if err != nil {
57121
return mcp.NewToolResultError(err.Error()), nil
58122
}
59123

60-
client, err := getGQLClient(ctx)
61-
if err != nil {
62-
return mcp.NewToolResultError(fmt.Sprintf("failed to get GitHub GQL client: %v", err)), nil
63-
}
64-
65-
// If category filter is specified, use it as the category ID for server-side filtering
66-
var categoryID *githubv4.ID
67-
if category != "" {
68-
id := githubv4.ID(category)
69-
categoryID = &id
70-
}
71-
72124
orderBy, err := OptionalParam[string](request, "orderBy")
73125
if err != nil {
74126
return mcp.NewToolResultError(err.Error()), nil
75127
}
76-
if orderBy == "" {
77-
orderBy = "CREATED_AT"
78-
}
128+
79129
direction, err := OptionalParam[string](request, "direction")
80130
if err != nil {
81131
return mcp.NewToolResultError(err.Error()), nil
82132
}
83-
if direction == "" {
84-
direction = "DESC"
85-
}
86-
87-
// Now execute the discussions query
88-
var discussions []*github.Discussion
89-
if categoryID != nil {
90-
// Query with category filter (server-side filtering)
91-
var query struct {
92-
Repository struct {
93-
Discussions struct {
94-
Nodes []struct {
95-
Number githubv4.Int
96-
Title githubv4.String
97-
CreatedAt githubv4.DateTime
98-
UpdatedAt githubv4.DateTime
99-
Author struct {
100-
Login githubv4.String
101-
}
102-
Category struct {
103-
Name githubv4.String
104-
} `graphql:"category"`
105-
URL githubv4.String `graphql:"url"`
106-
}
107-
} `graphql:"discussions(first: 100, categoryId: $categoryId, orderBy: {field: $orderByField, direction: $direction})"`
108-
} `graphql:"repository(owner: $owner, name: $repo)"`
109-
}
110-
111-
112-
vars := map[string]interface{}{
113-
"owner": githubv4.String(owner),
114-
"repo": githubv4.String(repo),
115-
"categoryId": *categoryID,
116-
"orderByField": githubv4.DiscussionOrderField(orderBy),
117-
"direction": githubv4.OrderDirection(direction),
118-
}
119-
120-
121-
if err := client.Query(ctx, &query, vars); err != nil {
122-
return mcp.NewToolResultError(err.Error()), nil
123-
}
124133

125-
// Map nodes to GitHub Discussion objects
126-
for _, n := range query.Repository.Discussions.Nodes {
127-
di := &github.Discussion{
128-
Number: github.Ptr(int(n.Number)),
129-
Title: github.Ptr(string(n.Title)),
130-
HTMLURL: github.Ptr(string(n.URL)),
131-
CreatedAt: &github.Timestamp{Time: n.CreatedAt.Time},
132-
UpdatedAt: &github.Timestamp{Time: n.UpdatedAt.Time},
133-
User: &github.User{
134-
Login: github.Ptr(string(n.Author.Login)),
135-
},
136-
DiscussionCategory: &github.DiscussionCategory{
137-
Name: github.Ptr(string(n.Category.Name)),
138-
},
139-
}
140-
discussions = append(discussions, di)
141-
}
142-
} else {
143-
// Query without category filter
144-
var query struct {
145-
Repository struct {
146-
Discussions struct {
147-
Nodes []struct {
148-
Number githubv4.Int
149-
Title githubv4.String
150-
CreatedAt githubv4.DateTime
151-
UpdatedAt githubv4.DateTime
152-
Author struct {
153-
Login githubv4.String
154-
}
155-
Category struct {
156-
Name githubv4.String
157-
} `graphql:"category"`
158-
URL githubv4.String `graphql:"url"`
159-
}
160-
} `graphql:"discussions(first: 100, orderBy: {field: $orderByField, direction: $direction})"`
161-
} `graphql:"repository(owner: $owner, name: $repo)"`
162-
}
134+
client, err := getGQLClient(ctx)
135+
if err != nil {
136+
return mcp.NewToolResultError(fmt.Sprintf("failed to get GitHub GQL client: %v", err)), nil
137+
}
163138

139+
baseVars := map[string]interface{}{
140+
"owner": githubv4.String(owner),
141+
"repo": githubv4.String(repo),
142+
}
164143

165-
vars := map[string]interface{}{
166-
"owner": githubv4.String(owner),
167-
"repo": githubv4.String(repo),
168-
"orderByField": githubv4.DiscussionOrderField(orderBy),
169-
"direction": githubv4.OrderDirection(direction),
170-
}
144+
// this is an extra check in case the tool description is misinterpreted, because
145+
// we shouldn't use ordering unless both a 'field' and 'direction' are provided
146+
useOrdering := orderBy != "" && direction != ""
147+
if useOrdering {
148+
orderObject := githubv4.DiscussionOrder{
149+
Field: githubv4.DiscussionOrderField(orderBy),
150+
Direction: githubv4.OrderDirection(direction),
151+
}
152+
baseVars["orderBy"] = orderObject
153+
}
171154

172-
if err := client.Query(ctx, &query, vars); err != nil {
173-
return mcp.NewToolResultError(err.Error()), nil
174-
}
155+
var discussions []*github.Discussion
156+
queries := &discussionQueries{}
175157

176-
// Map nodes to GitHub Discussion objects
177-
for _, n := range query.Repository.Discussions.Nodes {
178-
di := &github.Discussion{
179-
Number: github.Ptr(int(n.Number)),
180-
Title: github.Ptr(string(n.Title)),
181-
HTMLURL: github.Ptr(string(n.URL)),
182-
CreatedAt: &github.Timestamp{Time: n.CreatedAt.Time},
183-
UpdatedAt: &github.Timestamp{Time: n.UpdatedAt.Time},
184-
User: &github.User{
185-
Login: github.Ptr(string(n.Author.Login)),
186-
},
187-
DiscussionCategory: &github.DiscussionCategory{
188-
Name: github.Ptr(string(n.Category.Name)),
189-
},
190-
}
191-
discussions = append(discussions, di)
192-
}
193-
}
158+
if category != "" {
159+
vars := make(map[string]interface{})
160+
for k, v := range baseVars {
161+
vars[k] = v
162+
}
163+
vars["categoryId"] = githubv4.ID(category)
164+
165+
if useOrdering {
166+
log.Printf("GraphQL Query with category and order: %+v", queries.WithCategoryAndOrder)
167+
log.Printf("GraphQL Variables: %+v", vars)
168+
169+
if err := client.Query(ctx, &queries.WithCategoryAndOrder, vars); err != nil {
170+
return mcp.NewToolResultError(err.Error()), nil
171+
}
172+
173+
for _, node := range queries.WithCategoryAndOrder.Repository.Discussions.Nodes {
174+
discussions = append(discussions, fragmentToDiscussion(node))
175+
}
176+
} else {
177+
log.Printf("GraphQL Query with category no order: %+v", queries.WithCategoryNoOrder)
178+
log.Printf("GraphQL Variables: %+v", vars)
179+
180+
if err := client.Query(ctx, &queries.WithCategoryNoOrder, vars); err != nil {
181+
return mcp.NewToolResultError(err.Error()), nil
182+
}
183+
184+
for _, node := range queries.WithCategoryNoOrder.Repository.Discussions.Nodes {
185+
discussions = append(discussions, fragmentToDiscussion(node))
186+
}
187+
}
188+
} else {
189+
if useOrdering {
190+
log.Printf("GraphQL Query basic with order: %+v", queries.BasicWithOrder)
191+
log.Printf("GraphQL Variables: %+v", baseVars)
192+
193+
if err := client.Query(ctx, &queries.BasicWithOrder, baseVars); err != nil {
194+
return mcp.NewToolResultError(err.Error()), nil
195+
}
196+
197+
for _, node := range queries.BasicWithOrder.Repository.Discussions.Nodes {
198+
discussions = append(discussions, fragmentToDiscussion(node))
199+
}
200+
} else {
201+
log.Printf("GraphQL Query basic no order: %+v", queries.BasicNoOrder)
202+
log.Printf("GraphQL Variables: %+v", baseVars)
203+
204+
if err := client.Query(ctx, &queries.BasicNoOrder, baseVars); err != nil {
205+
return mcp.NewToolResultError(err.Error()), nil
206+
}
207+
208+
for _, node := range queries.BasicNoOrder.Repository.Discussions.Nodes {
209+
discussions = append(discussions, fragmentToDiscussion(node))
210+
}
211+
}
212+
}
194213

195214
// Marshal and return
196215
out, err := json.Marshal(discussions)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy