Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Tommy/expand-list-discussions-tool#690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
tommaso-moro wants to merge19 commits intogithub:main
base:main
Choose a base branch
Loading
fromtommaso-moro:tommy/expand-discussions-tools
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
19 commits
Select commitHold shift + click to select a range
687ee8f
added updatedAt and Author (aka User) login to query and payload
tommaso-moroJul 14, 2025
dbbf724
added initial support for orderby and direction
tommaso-moroJul 15, 2025
34a413d
sort by created at instead of updated at by default
tommaso-moroJul 15, 2025
5d7230d
remove unused code
tommaso-moroJul 15, 2025
c1afb88
refactor to map to most suitable query based on user inputs at runtime
tommaso-moroJul 16, 2025
7eeb87c
updated readme with new description
tommaso-moroJul 17, 2025
44f8f35
restore original categoryID code, simplify vars management
tommaso-moroJul 17, 2025
bcb82c8
quick fix
tommaso-moroJul 17, 2025
751dfa5
update tests to account for recent changes (author login, updated at …
tommaso-moroJul 17, 2025
b064f7a
use switch statement for better readability
tommaso-moroJul 17, 2025
6ab5137
remove comment
tommaso-moroJul 17, 2025
25d39be
linting
tommaso-moroJul 17, 2025
14dcf32
refactored logic, simplified switch statement
tommaso-moroJul 17, 2025
49d0dea
linting
tommaso-moroJul 17, 2025
eb78fe9
use original queries from discussions list tool for testing
tommaso-moroJul 17, 2025
d499668
linting
tommaso-moroJul 17, 2025
c9a0572
Merge branch 'main' into tommy/expand-discussions-tools
tommaso-moroJul 17, 2025
de8583c
remove logging
tommaso-moroJul 17, 2025
2783724
Merge branch 'tommy/expand-discussions-tools' of https://github.com/t…
tommaso-moroJul 17, 2025
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
2 changes: 2 additions & 0 deletionsREADME.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,6 +595,8 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description

-**list_discussions** - List discussions
-`category`: Optional filter by discussion category ID. If provided, only discussions with this category are listed. (string, optional)
-`direction`: Order direction. (string, optional)
-`orderBy`: Order discussions by field. If provided, the 'direction' also needs to be provided. (string, optional)
-`owner`: Repository owner (string, required)
-`repo`: Repository name (string, required)

Expand Down
208 changes: 132 additions & 76 deletionspkg/github/discussions.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,81 @@ import (
"github.com/shurcooL/githubv4"
)

type DiscussionFragment struct {
Number githubv4.Int
Title githubv4.String
CreatedAt githubv4.DateTime
UpdatedAt githubv4.DateTime
Author struct {
Login githubv4.String
}
Category struct {
Name githubv4.String
} `graphql:"category"`
URL githubv4.String `graphql:"url"`
}

type BasicNoOrder struct {
Repository struct {
Discussions struct {
Nodes []DiscussionFragment
} `graphql:"discussions(first: 100)"`
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type BasicWithOrder struct {
Repository struct {
Discussions struct {
Nodes []DiscussionFragment
} `graphql:"discussions(first: 100, orderBy: { field: $orderByField, direction: $orderByDirection })"`
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type WithCategoryAndOrder struct {
Repository struct {
Discussions struct {
Nodes []DiscussionFragment
} `graphql:"discussions(first: 100, categoryId: $categoryId, orderBy: { field: $orderByField, direction: $orderByDirection })"`
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type WithCategoryNoOrder struct {
Repository struct {
Discussions struct {
Nodes []DiscussionFragment
} `graphql:"discussions(first: 100, categoryId: $categoryId)"`
} `graphql:"repository(owner: $owner, name: $repo)"`
}

func fragmentToDiscussion(fragment DiscussionFragment) *github.Discussion {
return &github.Discussion{
Number: github.Ptr(int(fragment.Number)),
Title: github.Ptr(string(fragment.Title)),
HTMLURL: github.Ptr(string(fragment.URL)),
CreatedAt: &github.Timestamp{Time: fragment.CreatedAt.Time},
UpdatedAt: &github.Timestamp{Time: fragment.UpdatedAt.Time},
User: &github.User{
Login: github.Ptr(string(fragment.Author.Login)),
},
DiscussionCategory: &github.DiscussionCategory{
Name: github.Ptr(string(fragment.Category.Name)),
},
}
}

func getQueryType(useOrdering bool, categoryID *githubv4.ID) any {
if categoryID != nil && useOrdering {
return &WithCategoryAndOrder{}
}
if categoryID != nil && !useOrdering {
return &WithCategoryNoOrder{}
}
if categoryID == nil && useOrdering {
return &BasicWithOrder{}
}
return &BasicNoOrder{}
}

func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("list_discussions",
mcp.WithDescription(t("TOOL_LIST_DISCUSSIONS_DESCRIPTION", "List discussions for a repository")),
Expand All@@ -31,9 +106,16 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
mcp.WithString("category",
mcp.Description("Optional filter by discussion category ID. If provided, only discussions with this category are listed."),
),
mcp.WithString("orderBy",
mcp.Description("Order discussions by field. If provided, the 'direction' also needs to be provided."),
mcp.Enum("CREATED_AT", "UPDATED_AT"),
),
mcp.WithString("direction",
mcp.Description("Order direction."),
mcp.Enum("ASC", "DESC"),
),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Required params
owner, err := RequiredParam[string](request, "owner")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
Expand All@@ -43,106 +125,80 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
return mcp.NewToolResultError(err.Error()), nil
}

// Optional params
category, err := OptionalParam[string](request, "category")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

orderBy, err := OptionalParam[string](request, "orderBy")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

direction, err := OptionalParam[string](request, "direction")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

client, err := getGQLClient(ctx)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to get GitHub GQL client: %v", err)), nil
}

// If category filter is specified, use it as the category ID for server-side filtering
var categoryID *githubv4.ID
if category != "" {
id := githubv4.ID(category)
categoryID = &id
}

// Now execute the discussions query
var discussions []*github.Discussion
vars := map[string]interface{}{
"owner": githubv4.String(owner),
"repo": githubv4.String(repo),
}

// this is an extra check in case the tool description is misinterpreted, because
// we shouldn't use ordering unless both a 'field' and 'direction' are provided
useOrdering := orderBy != "" && direction != ""
if useOrdering {
vars["orderByField"] = githubv4.DiscussionOrderField(orderBy)
vars["orderByDirection"] = githubv4.OrderDirection(direction)
}

if categoryID != nil {
// Query with category filter (server-side filtering)
var query struct {
Repository struct {
Discussions struct {
Nodes []struct {
Number githubv4.Int
Title githubv4.String
CreatedAt githubv4.DateTime
Category struct {
Name githubv4.String
} `graphql:"category"`
URL githubv4.String `graphql:"url"`
}
} `graphql:"discussions(first: 100, categoryId: $categoryId)"`
} `graphql:"repository(owner: $owner, name: $repo)"`
}
vars := map[string]interface{}{
"owner": githubv4.String(owner),
"repo": githubv4.String(repo),
"categoryId": *categoryID,
}
if err := client.Query(ctx, &query, vars); err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
vars["categoryId"] = *categoryID
}

// Map nodes to GitHub Discussion objects
for _, n := range query.Repository.Discussions.Nodes {
di := &github.Discussion{
Number: github.Ptr(int(n.Number)),
Title: github.Ptr(string(n.Title)),
HTMLURL: github.Ptr(string(n.URL)),
CreatedAt: &github.Timestamp{Time: n.CreatedAt.Time},
DiscussionCategory: &github.DiscussionCategory{
Name: github.Ptr(string(n.Category.Name)),
},
}
discussions = append(discussions, di)
}
} else {
// Query without category filter
var query struct {
Repository struct {
Discussions struct {
Nodes []struct {
Number githubv4.Int
Title githubv4.String
CreatedAt githubv4.DateTime
Category struct {
Name githubv4.String
} `graphql:"category"`
URL githubv4.String `graphql:"url"`
}
} `graphql:"discussions(first: 100)"`
} `graphql:"repository(owner: $owner, name: $repo)"`
var discussions []*github.Discussion
discussionQuery := getQueryType(useOrdering, categoryID)

if err := client.Query(ctx, discussionQuery, vars); err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

// we need to check what user inputs we received at runtime, and use the
// most appropriate query based on that
switch queryType := discussionQuery.(type) {
case *WithCategoryAndOrder:
for _, node := range queryType.Repository.Discussions.Nodes {
discussions = append(discussions, fragmentToDiscussion(node))
}
vars := map[string]interface{}{
"owner": githubv4.String(owner),
"repo": githubv4.String(repo),

case *WithCategoryNoOrder:
for _, node := range queryType.Repository.Discussions.Nodes {
discussions = append(discussions, fragmentToDiscussion(node))
}
if err := client.Query(ctx, &query, vars); err != nil {
return mcp.NewToolResultError(err.Error()), nil

case *BasicWithOrder:
for _, node := range queryType.Repository.Discussions.Nodes {
discussions = append(discussions, fragmentToDiscussion(node))
}

// Map nodes to GitHub Discussion objects
for _, n := range query.Repository.Discussions.Nodes {
di := &github.Discussion{
Number: github.Ptr(int(n.Number)),
Title: github.Ptr(string(n.Title)),
HTMLURL: github.Ptr(string(n.URL)),
CreatedAt: &github.Timestamp{Time: n.CreatedAt.Time},
DiscussionCategory: &github.DiscussionCategory{
Name: github.Ptr(string(n.Category.Name)),
},
}
discussions = append(discussions, di)
case *BasicNoOrder:
for _, node := range queryType.Repository.Discussions.Nodes {
discussions = append(discussions, fragmentToDiscussion(node))
}
}

// Marshal and return
out, err := json.Marshal(discussions)
if err != nil {
return nil, fmt.Errorf("failed to marshal discussions: %w", err)
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp