feat(issue): add get_discussion_markdown to issue_read

Adds a new issue_read method that fetches an issue and its comments
and renders them as a single Markdown document, instead of a JSON
array. Formatting logic lives in a pure, unit-tested helper
(formatDiscussionMarkdown) covering comment rendering, empty comment
lists, and attachment-inlined bodies. Existing get/get_comments/
get_labels methods are unchanged.

Co-Authored-By: Codet <codet@commitgo.dev> (GPT-5-Codex)
This commit is contained in:
Lunny Xiao
2026-08-24 00:14:50 -07:00
parent 815a0e26aa
commit 3c6d2ecd6b
5 changed files with 324 additions and 2 deletions
+52 -2
View File
@@ -55,9 +55,9 @@ var (
IssueReadTool = tool.NewDefinition(
IssueReadToolName,
"Read issue: details, comments, or labels.",
"Read issue: details, comments, labels, or the full discussion as Markdown.",
annotation.ReadOnly("Read issue details"),
tool.String("method", tool.Required(), tool.Enum("get", "get_comments", "get_labels")),
tool.String("method", tool.Required(), tool.Enum("get", "get_comments", "get_labels", "get_discussion_markdown")),
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
tool.Number("issue_number", tool.Required()),
@@ -112,6 +112,8 @@ func issueReadFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult,
return getIssueCommentsByIndexFn(ctx, args)
case "get_labels":
return getIssueLabelsFn(ctx, args)
case "get_discussion_markdown":
return getIssueDiscussionMarkdownFn(ctx, args)
default:
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
}
@@ -398,6 +400,54 @@ func getIssueCommentsByIndexFn(ctx context.Context, args map[string]any) (*mcp.C
return to.TextResult(out)
}
func getIssueDiscussionMarkdownFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "issue_number")
if err != nil {
return to.ErrorResult(err)
}
var issue issueWithAssets
issuePath := fmt.Sprintf("repos/%s/%s/issues/%d", url.PathEscape(owner), url.PathEscape(repo), index)
if _, err := gitea.DoJSON(ctx, "GET", issuePath, nil, nil, &issue); err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err))
}
var comments []commentWithAssets
commentsPath := fmt.Sprintf("repos/%s/%s/issues/%d/comments", url.PathEscape(owner), url.PathEscape(repo), index)
if _, err := gitea.DoJSON(ctx, "GET", commentsPath, nil, nil, &comments); err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, index, err))
}
discComments := make([]discussionComment, 0, len(comments))
for i := range comments {
discComments = append(discComments, discussionComment{
Author: slim.UserLogin(comments[i].Poster),
CreatedAt: comments[i].Created,
Body: slim.BodyWithAttachments(comments[i].Body, comments[i].Assets),
})
}
md := formatDiscussionMarkdown(discussionIssue{
Number: issue.Index,
Title: issue.Title,
Author: slim.UserLogin(issue.Poster),
State: string(issue.State),
Labels: slim.LabelNames(issue.Labels),
Body: slim.BodyWithAttachments(issue.Body, issue.Assets),
CreatedAt: issue.Created,
}, discComments)
return to.RawTextResult(md)
}
func getIssueLabelsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
owner, err := params.GetString(args, "owner")
if err != nil {