using GiteaMcp.Services; using ModelContextProtocol.Server; using System.ComponentModel; namespace GiteaMcp.Tools; /// Issue Tool:list_issues / read_issue [McpServerToolType] public class IssueTools( GiteaApiClient gitea, GiteaRepoFilter filter) { [McpServerTool] [Description( "List issues in a Gitea repository. " + "state can be 'open' (default), 'closed', or 'all'. " + "Returns: issue number, title, state, labels, assignees, created_at, comment count, and URL. " + "Use read_issue to get the full body and comments of a specific issue.")] public async Task list_issues( [Description("Repository owner.")] string owner, [Description("Repository name.")] string repo, [Description("Filter by state: 'open', 'closed', or 'all'. Default 'open'.")] string? state = null, [Description("Max issues to return. Default 30.")] int? limit = null, CancellationToken ct = default) { if (filter.IsBlocked($"{owner}/{repo}")) throw new UnauthorizedAccessException($"Repo {owner}/{repo} is on the access blocklist."); var st = state ?? "open"; var lim = Math.Min(limit ?? 30, 50); var issues = await gitea.GetIssuesAsync(owner, repo, st, lim, ct); return issues.Select(i => new { number = i.Number, title = i.Title, state = i.State, html_url = i.HtmlUrl, author = i.User?.Login, labels = i.Labels?.Select(l => l.Name).ToList() ?? [], assignees = i.Assignees?.Select(a => a.Login).ToList() ?? [], comments = i.Comments, created_at = i.CreatedAt, updated_at = i.UpdatedAt, closed_at = i.ClosedAt, }).ToList(); } [McpServerTool] [Description( "Get the full body and all comments of a specific Gitea issue. " + "Use list_issues first to find the issue number. " + "Returns: title, body (Markdown), state, labels, and all comment texts with authors and timestamps.")] public async Task read_issue( [Description("Repository owner.")] string owner, [Description("Repository name.")] string repo, [Description("Issue number (integer, e.g. 42).")] int number, CancellationToken ct = default) { if (filter.IsBlocked($"{owner}/{repo}")) throw new UnauthorizedAccessException($"Repo {owner}/{repo} is on the access blocklist."); var issue = await gitea.GetIssueAsync(owner, repo, number, ct); var comments = await gitea.GetIssueCommentsAsync(owner, repo, number, ct); return new { number = issue.Number, title = issue.Title, body = issue.Body, state = issue.State, html_url = issue.HtmlUrl, author = issue.User?.Login, labels = issue.Labels?.Select(l => l.Name).ToList() ?? [], assignees = issue.Assignees?.Select(a => a.Login).ToList() ?? [], created_at = issue.CreatedAt, updated_at = issue.UpdatedAt, closed_at = issue.ClosedAt, comments = comments.Select(c => new { id = c.Id, author = c.User?.Login, body = c.Body, created_at = c.CreatedAt, updated_at = c.UpdatedAt, }).ToList(), }; } }