44 "context"
55 "encoding/json"
66 "fmt"
7- "time"
87
98 "github.com/github/github-mcp-server/pkg/translations"
109 "github.com/go-viper/mapstructure/v2"
@@ -80,142 +79,121 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
8079 mcp .Description ("Repository name" ),
8180 ),
8281 mcp .WithString ("category" ,
83- mcp .Description ("Category filter (name)" ),
84- ),
85- mcp .WithString ("since" ,
86- mcp .Description ("Filter by date (ISO 8601 timestamp)" ),
87- ),
88- mcp .WithString ("sort" ,
89- mcp .Description ("Sort field" ),
90- mcp .DefaultString ("CREATED_AT" ),
91- mcp .Enum ("CREATED_AT" , "UPDATED_AT" ),
92- ),
93- mcp .WithString ("direction" ,
94- mcp .Description ("Sort direction" ),
95- mcp .DefaultString ("DESC" ),
96- mcp .Enum ("ASC" , "DESC" ),
97- ),
98- mcp .WithNumber ("first" ,
99- mcp .Description ("Number of discussions to return per page (min 1, max 100)" ),
100- mcp .Min (1 ),
101- mcp .Max (100 ),
102- ),
103- mcp .WithNumber ("last" ,
104- mcp .Description ("Number of discussions to return from the end (min 1, max 100)" ),
105- mcp .Min (1 ),
106- mcp .Max (100 ),
107- ),
108- mcp .WithString ("after" ,
109- mcp .Description ("Cursor for pagination, use the 'after' field from the previous response" ),
110- ),
111- mcp .WithString ("before" ,
112- mcp .Description ("Cursor for pagination, use the 'before' field from the previous response" ),
113- ),
114- mcp .WithBoolean ("answered" ,
115- mcp .Description ("Filter by whether discussions have been answered or not" ),
82+ mcp .Description ("Optional filter by discussion category ID. If provided, only discussions with this category are listed." ),
11683 ),
11784 ),
11885 func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
119- // Decode params
120- var params struct {
121- Owner string
122- Repo string
123- Category string
124- Since string
125- Sort string
126- Direction string
127- First int32
128- Last int32
129- After string
130- Before string
131- Answered bool
132- }
133- if err := mapstructure .Decode (request .Params .Arguments , & params ); err != nil {
86+ // Required params
87+ owner , err := RequiredParam [string ](request , "owner" )
88+ if err != nil {
13489 return mcp .NewToolResultError (err .Error ()), nil
13590 }
136- if params .First != 0 && params .Last != 0 {
137- return mcp .NewToolResultError ("only one of 'first' or 'last' may be specified" ), nil
138- }
139- if params .After != "" && params .Before != "" {
140- return mcp .NewToolResultError ("only one of 'after' or 'before' may be specified" ), nil
141- }
142- if params .After != "" && params .Last != 0 {
143- return mcp .NewToolResultError ("'after' cannot be used with 'last'. Did you mean to use 'before' instead?" ), nil
91+ repo , err := RequiredParam [string ](request , "repo" )
92+ if err != nil {
93+ return mcp .NewToolResultError (err .Error ()), nil
14494 }
145- if params .Before != "" && params .First != 0 {
146- return mcp .NewToolResultError ("'before' cannot be used with 'first'. Did you mean to use 'after' instead?" ), nil
95+
96+ // Optional params
97+ category , err := OptionalParam [string ](request , "category" )
98+ if err != nil {
99+ return mcp .NewToolResultError (err .Error ()), nil
147100 }
148- // Get GraphQL client
101+
149102 client , err := getGQLClient (ctx )
150103 if err != nil {
151104 return mcp .NewToolResultError (fmt .Sprintf ("failed to get GitHub GQL client: %v" , err )), nil
152105 }
153- // Prepare GraphQL query
154- var q struct {
155- Repository struct {
156- Discussions struct {
157- Nodes []struct {
158- Number githubv4.Int
159- Title githubv4.String
160- CreatedAt githubv4.DateTime
161- Category struct {
162- Name githubv4.String
163- } `graphql:"category"`
164- URL githubv4.String `graphql:"url"`
165- }
166- } `graphql:"discussions(categoryId: $categoryId, orderBy: {field: $sort, direction: $direction}, first: $first, after: $after, last: $last, before: $before, answered: $answered)"`
167- } `graphql:"repository(owner: $owner, name: $repo)"`
168- }
169- categories , err := GetAllDiscussionCategories (ctx , client , params .Owner , params .Repo )
170- if err != nil {
171- return mcp .NewToolResultError (fmt .Sprintf ("failed to get discussion categories: %v" , err )), nil
172- }
173- var categoryID githubv4.ID = categories [params .Category ]
174- if categoryID == "" && params .Category != "" {
175- return mcp .NewToolResultError (fmt .Sprintf ("category '%s' not found" , params .Category )), nil
176- }
177- // Build query variables
178- vars := map [string ]interface {}{
179- "owner" : githubv4 .String (params .Owner ),
180- "repo" : githubv4 .String (params .Repo ),
181- "categoryId" : categoryID ,
182- "sort" : githubv4 .DiscussionOrderField (params .Sort ),
183- "direction" : githubv4 .OrderDirection (params .Direction ),
184- "first" : githubv4 .Int (params .First ),
185- "last" : githubv4 .Int (params .Last ),
186- "after" : githubv4 .String (params .After ),
187- "before" : githubv4 .String (params .Before ),
188- "answered" : githubv4 .Boolean (params .Answered ),
189- }
190- // Execute query
191- if err := client .Query (ctx , & q , vars ); err != nil {
192- return mcp .NewToolResultError (err .Error ()), nil
106+
107+ // If category filter is specified, use it as the category ID for server-side filtering
108+ var categoryID * githubv4.ID
109+ if category != "" {
110+ id := githubv4 .ID (category )
111+ categoryID = & id
193112 }
194- // Map nodes to GitHub Issue objects - there is no discussion type in the GitHub API, so we use Issue to benefit from existing code
113+
114+ // Now execute the discussions query
195115 var discussions []* github.Issue
196- for _ , n := range q .Repository .Discussions .Nodes {
197- di := & github.Issue {
198- Number : github .Ptr (int (n .Number )),
199- Title : github .Ptr (string (n .Title )),
200- HTMLURL : github .Ptr (string (n .URL )),
201- CreatedAt : & github.Timestamp {Time : n .CreatedAt .Time },
116+ if categoryID != nil {
117+ // Query with category filter (server-side filtering)
118+ var query struct {
119+ Repository struct {
120+ Discussions struct {
121+ Nodes []struct {
122+ Number githubv4.Int
123+ Title githubv4.String
124+ CreatedAt githubv4.DateTime
125+ Category struct {
126+ Name githubv4.String
127+ } `graphql:"category"`
128+ URL githubv4.String `graphql:"url"`
129+ }
130+ } `graphql:"discussions(first: 100, categoryId: $categoryId)"`
131+ } `graphql:"repository(owner: $owner, name: $repo)"`
132+ }
133+ vars := map [string ]interface {}{
134+ "owner" : githubv4 .String (owner ),
135+ "repo" : githubv4 .String (repo ),
136+ "categoryId" : * categoryID ,
137+ }
138+ if err := client .Query (ctx , & query , vars ); err != nil {
139+ return mcp .NewToolResultError (err .Error ()), nil
202140 }
203- discussions = append (discussions , di )
204- }
205141
206- // Post filtering discussions based on 'since' parameter
207- if params .Since != "" {
208- sinceTime , err := time .Parse (time .RFC3339 , params .Since )
209- if err != nil {
210- return mcp .NewToolResultError (fmt .Sprintf ("invalid 'since' timestamp: %v" , err )), nil
142+ // Map nodes to GitHub Issue objects
143+ for _ , n := range query .Repository .Discussions .Nodes {
144+ di := & github.Issue {
145+ Number : github .Ptr (int (n .Number )),
146+ Title : github .Ptr (string (n .Title )),
147+ HTMLURL : github .Ptr (string (n .URL )),
148+ CreatedAt : & github.Timestamp {Time : n .CreatedAt .Time },
149+ Labels : []* github.Label {
150+ {
151+ Name : github .Ptr (fmt .Sprintf ("category:%s" , string (n .Category .Name ))),
152+ },
153+ },
154+ }
155+ discussions = append (discussions , di )
156+ }
157+ } else {
158+ // Query without category filter
159+ var query struct {
160+ Repository struct {
161+ Discussions struct {
162+ Nodes []struct {
163+ Number githubv4.Int
164+ Title githubv4.String
165+ CreatedAt githubv4.DateTime
166+ Category struct {
167+ Name githubv4.String
168+ } `graphql:"category"`
169+ URL githubv4.String `graphql:"url"`
170+ }
171+ } `graphql:"discussions(first: 100)"`
172+ } `graphql:"repository(owner: $owner, name: $repo)"`
211173 }
212- var filteredDiscussions []* github.Issue
213- for _ , d := range discussions {
214- if d .CreatedAt .Time .After (sinceTime ) {
215- filteredDiscussions = append (filteredDiscussions , d )
174+ vars := map [string ]interface {}{
175+ "owner" : githubv4 .String (owner ),
176+ "repo" : githubv4 .String (repo ),
177+ }
178+ if err := client .Query (ctx , & query , vars ); err != nil {
179+ return mcp .NewToolResultError (err .Error ()), nil
180+ }
181+
182+ // Map nodes to GitHub Issue objects
183+ for _ , n := range query .Repository .Discussions .Nodes {
184+ di := & github.Issue {
185+ Number : github .Ptr (int (n .Number )),
186+ Title : github .Ptr (string (n .Title )),
187+ HTMLURL : github .Ptr (string (n .URL )),
188+ CreatedAt : & github.Timestamp {Time : n .CreatedAt .Time },
189+ Labels : []* github.Label {
190+ {
191+ Name : github .Ptr (fmt .Sprintf ("category:%s" , string (n .Category .Name ))),
192+ },
193+ },
216194 }
195+ discussions = append (discussions , di )
217196 }
218- discussions = filteredDiscussions
219197 }
220198
221199 // Marshal and return
@@ -270,6 +248,9 @@ func GetDiscussion(getGQLClient GetGQLClientFn, t translations.TranslationHelper
270248 State githubv4.String
271249 CreatedAt githubv4.DateTime
272250 URL githubv4.String `graphql:"url"`
251+ Category struct {
252+ Name githubv4.String
253+ } `graphql:"category"`
273254 } `graphql:"discussion(number: $discussionNumber)"`
274255 } `graphql:"repository(owner: $owner, name: $repo)"`
275256 }
@@ -288,6 +269,11 @@ func GetDiscussion(getGQLClient GetGQLClientFn, t translations.TranslationHelper
288269 State : github .Ptr (string (d .State )),
289270 HTMLURL : github .Ptr (string (d .URL )),
290271 CreatedAt : & github.Timestamp {Time : d .CreatedAt .Time },
272+ Labels : []* github.Label {
273+ {
274+ Name : github .Ptr (fmt .Sprintf ("category:%s" , string (d .Category .Name ))),
275+ },
276+ },
291277 }
292278 out , err := json .Marshal (discussion )
293279 if err != nil {
@@ -429,16 +415,12 @@ func ListDiscussionCategories(getGQLClient GetGQLClientFn, t translations.Transl
429415 ID githubv4.ID
430416 Name githubv4.String
431417 }
432- } `graphql:"discussionCategories(first: $first, last: $last, after: $after, before: $before )"`
418+ } `graphql:"discussionCategories(first: 100 )"`
433419 } `graphql:"repository(owner: $owner, name: $repo)"`
434420 }
435421 vars := map [string ]interface {}{
436- "owner" : githubv4 .String (params .Owner ),
437- "repo" : githubv4 .String (params .Repo ),
438- "first" : githubv4 .Int (params .First ),
439- "last" : githubv4 .Int (params .Last ),
440- "after" : githubv4 .String (params .After ),
441- "before" : githubv4 .String (params .Before ),
422+ "owner" : githubv4 .String (params .Owner ),
423+ "repo" : githubv4 .String (params .Repo ),
442424 }
443425 if err := client .Query (ctx , & q , vars ); err != nil {
444426 return mcp .NewToolResultError (err .Error ()), nil
0 commit comments