Skip to main content
Glama
attach.go10 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // Copyright © 2025 Ronmi Ren <ronmi.ren@gmail.com> package issue import ( "context" "fmt" "strconv" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/raohwork/forgejo-mcp/tools" "github.com/raohwork/forgejo-mcp/types" ) // ListIssueAttachmentsParams defines the parameters for the list_issue_attachments tool. // It specifies the issue from which to list attachments. type ListIssueAttachmentsParams struct { // Owner is the username or organization name that owns the repository. Owner string `json:"owner"` // Repo is the name of the repository. Repo string `json:"repo"` // Index is the issue number. Index int `json:"index"` } // ListIssueAttachmentsImpl implements the read-only MCP tool for listing issue attachments. // This is a safe, idempotent operation. Note: This feature is not supported by the // official Forgejo SDK and requires a custom HTTP implementation. type ListIssueAttachmentsImpl struct { Client *tools.Client } // Definition describes the `list_issue_attachments` tool. It requires `owner`, `repo`, // and the issue `index`. It is marked as a safe, read-only operation. func (ListIssueAttachmentsImpl) Definition() *mcp.Tool { return &mcp.Tool{ Name: "list_issue_attachments", Title: "List Issue Attachments", Description: "List all attachments on an issue. Returns attachment information including names, sizes, and download URLs.", Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: true, IdempotentHint: true, }, InputSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "owner": { Type: "string", Description: "Repository owner (username or organization name)", }, "repo": { Type: "string", Description: "Repository name", }, "index": { Type: "integer", Description: "Issue index number", }, }, Required: []string{"owner", "repo", "index"}, }, } } // Handler implements the logic for listing issue attachments. It performs a custom // HTTP GET request to the `/repos/{owner}/{repo}/issues/{index}/assets` // endpoint and formats the results into a markdown list. func (impl ListIssueAttachmentsImpl) Handler() mcp.ToolHandlerFor[ListIssueAttachmentsParams, any] { return func(ctx context.Context, req *mcp.CallToolRequest, args ListIssueAttachmentsParams) (*mcp.CallToolResult, any, error) { p := args // List issue attachments using the custom client method attachments, err := impl.Client.MyListIssueAttachments(p.Owner, p.Repo, int64(p.Index)) if err != nil { return nil, nil, fmt.Errorf("failed to list issue attachments: %w", err) } // Convert to types.AttachmentList for consistent formatting attachmentList := make(types.AttachmentList, len(attachments)) for i, attachment := range attachments { attachmentList[i] = &types.Attachment{Attachment: attachment} } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: fmt.Sprintf("# Issue #%d Attachments\n\n%s", p.Index, attachmentList.ToMarkdown()), }, }, }, nil, nil } } // DeleteIssueAttachmentParams defines the parameters for deleting an issue attachment. // It specifies the attachment to be deleted by its ID. type DeleteIssueAttachmentParams struct { // Owner is the username or organization name that owns the repository. Owner string `json:"owner"` // Repo is the name of the repository. Repo string `json:"repo"` // Index is the issue number containing the attachment. Index int `json:"index"` // AttachmentID is the unique identifier of the attachment to delete. AttachmentID string `json:"attachment_id"` } // DeleteIssueAttachmentImpl implements the destructive MCP tool for deleting an issue attachment. // This is an idempotent but irreversible operation. Note: This feature is not supported // by the official Forgejo SDK and requires a custom HTTP implementation. type DeleteIssueAttachmentImpl struct { Client *tools.Client } // Definition describes the `delete_issue_attachment` tool. It requires the issue `index` // and `attachment_id`. It is marked as a destructive operation. func (DeleteIssueAttachmentImpl) Definition() *mcp.Tool { return &mcp.Tool{ Name: "delete_issue_attachment", Title: "Delete Issue Attachment", Description: "Delete a specific attachment from an issue.", Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: false, DestructiveHint: tools.BoolPtr(true), IdempotentHint: true, }, InputSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "owner": { Type: "string", Description: "Repository owner (username or organization name)", }, "repo": { Type: "string", Description: "Repository name", }, "index": { Type: "integer", Description: "Issue index number", }, "attachment_id": { Type: "string", Description: "Attachment ID to delete", }, }, Required: []string{"owner", "repo", "index", "attachment_id"}, }, } } // Handler implements the logic for deleting an issue attachment. It performs a custom // HTTP DELETE request to the `/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}` // endpoint. On success, it returns a simple text confirmation. func (impl DeleteIssueAttachmentImpl) Handler() mcp.ToolHandlerFor[DeleteIssueAttachmentParams, any] { return func(ctx context.Context, req *mcp.CallToolRequest, args DeleteIssueAttachmentParams) (*mcp.CallToolResult, any, error) { p := args // Convert attachment ID from string to int64 attachmentID, err := strconv.ParseInt(p.AttachmentID, 10, 64) if err != nil { return nil, nil, fmt.Errorf("invalid attachment ID: %w", err) } // Delete the attachment using the custom client method err = impl.Client.MyDeleteIssueAttachment(p.Owner, p.Repo, int64(p.Index), attachmentID) if err != nil { return nil, nil, fmt.Errorf("failed to delete issue attachment: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: fmt.Sprintf("Issue attachment %s deleted successfully from issue #%d", p.AttachmentID, p.Index), }, }, }, nil, nil } } // EditIssueAttachmentParams defines the parameters for editing an issue attachment. // It specifies the attachment to edit and its new name. type EditIssueAttachmentParams struct { // Owner is the username or organization name that owns the repository. Owner string `json:"owner"` // Repo is the name of the repository. Repo string `json:"repo"` // Index is the issue number containing the attachment. Index int `json:"index"` // AttachmentID is the unique identifier of the attachment to edit. AttachmentID string `json:"attachment_id"` // Name is the new display name for the attachment. Name string `json:"name"` } // EditIssueAttachmentImpl implements the MCP tool for editing an issue attachment. // This is an idempotent operation. Note: This feature is not supported by the // official Forgejo SDK and requires a custom HTTP implementation. type EditIssueAttachmentImpl struct { Client *tools.Client } // Definition describes the `edit_issue_attachment` tool. It requires the issue `index`, // `attachment_id`, and a new `name`. It is marked as idempotent. func (EditIssueAttachmentImpl) Definition() *mcp.Tool { return &mcp.Tool{ Name: "edit_issue_attachment", Title: "Edit Issue Attachment", Description: "Edit an attachment's metadata such as display name.", Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: false, DestructiveHint: tools.BoolPtr(false), IdempotentHint: true, }, InputSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "owner": { Type: "string", Description: "Repository owner (username or organization name)", }, "repo": { Type: "string", Description: "Repository name", }, "index": { Type: "integer", Description: "Issue index number", }, "attachment_id": { Type: "string", Description: "Attachment ID to edit", }, "name": { Type: "string", Description: "New display name for the attachment", }, }, Required: []string{"owner", "repo", "index", "attachment_id", "name"}, }, } } // Handler implements the logic for editing an issue attachment. It performs a custom // HTTP PATCH request to the `/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}` // endpoint. It will return an error if the attachment is not found. func (impl EditIssueAttachmentImpl) Handler() mcp.ToolHandlerFor[EditIssueAttachmentParams, any] { return func(ctx context.Context, req *mcp.CallToolRequest, args EditIssueAttachmentParams) (*mcp.CallToolResult, any, error) { p := args // Convert attachment ID from string to int64 attachmentID, err := strconv.ParseInt(p.AttachmentID, 10, 64) if err != nil { return nil, nil, fmt.Errorf("invalid attachment ID: %w", err) } // Create options struct from parameters options := tools.MyEditAttachmentOptions{ Name: p.Name, } // Edit the attachment using the custom client method attachment, err := impl.Client.MyEditIssueAttachment(p.Owner, p.Repo, int64(p.Index), attachmentID, options) if err != nil { return nil, nil, fmt.Errorf("failed to edit issue attachment: %w", err) } // Convert to types.Attachment for consistent formatting result := &types.Attachment{Attachment: attachment} return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: fmt.Sprintf("# Issue Attachment Updated\n\n%s", result.ToMarkdown()), }, }, }, nil, nil } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/raohwork/forgejo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server