Inline issue/comment attachments in body (#183)
The Gitea API returns an `assets` array on issue and comment responses, but the SDK structs drop it — so attachments are invisible to MCP agents.
Append each attachment as a `[name](url)` markdown link at the end of the body, mirroring how GitHub embeds attachments inline (which `github-mcp-server` preserves as-is).
**Coverage:**
- `issue_read get` — issue body attachments
- `issue_read get_comments` — issue and PR conversation comment attachments (same endpoint)
- `pull_request_read get` — PR description attachments (Gitea's `/pulls/` endpoint omits `assets`, so a follow-up best-effort call to `/issues/{n}/assets` surfaces them; PRs are issues internally)
PR review summaries and line-comment reviews don't support attachments per the Gitea API spec, so nothing to do there.
Closes https://gitea.com/gitea/gitea-mcp/issues/182
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/183
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
+29
-17
@@ -3,6 +3,7 @@ package issue
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/log"
|
||||
@@ -15,6 +16,18 @@ import (
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
// issueWithAssets / commentWithAssets wrap the SDK types to capture the
|
||||
// `assets` field that the SDK currently drops on these endpoints.
|
||||
type issueWithAssets struct {
|
||||
gitea_sdk.Issue
|
||||
Assets []*gitea_sdk.Attachment `json:"assets"`
|
||||
}
|
||||
|
||||
type commentWithAssets struct {
|
||||
gitea_sdk.Comment
|
||||
Assets []*gitea_sdk.Attachment `json:"assets"`
|
||||
}
|
||||
|
||||
var Tool = tool.New()
|
||||
|
||||
const (
|
||||
@@ -142,16 +155,14 @@ func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
issue, _, err := client.GetIssue(owner, repo, index)
|
||||
if err != nil {
|
||||
var issue issueWithAssets
|
||||
path := fmt.Sprintf("repos/%s/%s/issues/%d", url.PathEscape(owner), url.PathEscape(repo), index)
|
||||
if _, err := gitea.DoJSON(ctx, "GET", path, nil, nil, &issue); err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimIssue(issue))
|
||||
m := slimIssue(&issue.Issue)
|
||||
m["body"] = bodyWithAttachments(issue.Body, issue.Assets)
|
||||
return to.TextResult(m)
|
||||
}
|
||||
|
||||
func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
@@ -377,17 +388,18 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
opt := gitea_sdk.ListIssueCommentOptions{}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
issue, _, err := client.ListIssueComments(owner, repo, index, opt)
|
||||
if err != nil {
|
||||
var comments []commentWithAssets
|
||||
path := fmt.Sprintf("repos/%s/%s/issues/%d/comments", url.PathEscape(owner), url.PathEscape(repo), index)
|
||||
if _, err := gitea.DoJSON(ctx, "GET", path, nil, nil, &comments); err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, index, err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimComments(issue))
|
||||
out := make([]map[string]any, 0, len(comments))
|
||||
for i := range comments {
|
||||
m := slimComment(&comments[i].Comment)
|
||||
m["body"] = bodyWithAttachments(comments[i].Body, comments[i].Assets)
|
||||
out = append(out, m)
|
||||
}
|
||||
return to.TextResult(out)
|
||||
}
|
||||
|
||||
func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
|
||||
Reference in New Issue
Block a user