gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

✨ (mcp): Add issue comment tools (listIssueComments, addIssueComment)

eeac486dec2fc15128a0ccd64905d853b46b77a1 · Michael Hainz · 2026-07-21T07:20:18Z

Changes

4 files changed, +81 -3

MODIFY docs/users/mcp.md +2 -1
diff --git a/docs/users/mcp.md b/docs/users/mcp.md
index 62497e3..bc649ec 100644
--- a/docs/users/mcp.md
+++ b/docs/users/mcp.md
@@ -70,8 +70,9 @@
70 70 |---|---|---|
71 71 | Repositories | `listRepositories`, `getRepository` | no (public repos) |
72 72 | | `createRepository`, `forkRepository`, `deleteRepository` | yes |
73 -| Issues | `listIssues`, `getIssue` | no (public repos) |
73 +| Issues | `listIssues`, `getIssue`, `listIssueComments` | no (public repos) |
74 74 | | `createIssue`, `updateIssueStatus`, `deleteIssue` | yes (owner or collaborator) |
75 +| | `addIssueComment` | yes (any reader of the repository) |
75 76 | Merge requests | `listMergeRequests`, `getMergeRequest`, `listMergeRequestComments` | no (public repos) |
76 77 | | `createMergeRequest`, `mergeMergeRequest`, `closeMergeRequest` | yes (owner or collaborator) |
77 78 | | `addMergeRequestComment` | yes (any reader of the repository) |
MODIFY src/main/java/de/workaround/api/ApiModels.java +10 -0
diff --git a/src/main/java/de/workaround/api/ApiModels.java b/src/main/java/de/workaround/api/ApiModels.java
index d8aa554..a942b47 100644
--- a/src/main/java/de/workaround/api/ApiModels.java
+++ b/src/main/java/de/workaround/api/ApiModels.java
@@ -4,6 +4,7 @@
4 4 import java.util.List;
5 5
6 6 import de.workaround.model.Issue;
7 +import de.workaround.model.IssueComment;
7 8 import de.workaround.model.MergeRequest;
8 9 import de.workaround.model.MergeRequestComment;
9 10 import de.workaround.model.Repository;
@@ -75,6 +76,15 @@
75 76 }
76 77 }
77 78
79 + public record IssueCommentView(String id, String body, String author, Instant createdAt)
80 + {
81 + public static IssueCommentView of(IssueComment comment)
82 + {
83 + return new IssueCommentView(comment.id.toString(), comment.body, comment.author.username,
84 + comment.createdAt);
85 + }
86 + }
87 +
78 88 public record UserView(String username, String displayName)
79 89 {
80 90 }
MODIFY src/main/java/de/workaround/mcp/IssueTools.java +30 -2
diff --git a/src/main/java/de/workaround/mcp/IssueTools.java b/src/main/java/de/workaround/mcp/IssueTools.java
index 2ba3e4d..a2e1d85 100644
--- a/src/main/java/de/workaround/mcp/IssueTools.java
+++ b/src/main/java/de/workaround/mcp/IssueTools.java
@@ -3,8 +3,10 @@
3 3 import java.util.List;
4 4
5 5 import de.workaround.api.ApiModels;
6 +import de.workaround.git.IssueCommentService;
6 7 import de.workaround.git.IssueService;
7 8 import de.workaround.model.Issue;
9 +import de.workaround.model.IssueComment;
8 10 import de.workaround.model.Repository;
9 11 import de.workaround.model.User;
10 12 import io.quarkiverse.mcp.server.Tool;
@@ -15,8 +17,9 @@
15 17 import jakarta.transaction.Transactional;
16 18
17 19 /**
18 - * MCP tools mirroring the issue REST endpoints. Issues are addressed by their per-repository number.
19 - * Reading follows repository visibility; creating, transitioning and deleting require a token and ownership.
20 + * MCP tools mirroring the issue REST endpoints, including free-text discussion comments. Issues are
21 + * addressed by their per-repository number. Reading follows repository visibility; creating, transitioning
22 + * and deleting require a token and ownership, while commenting only requires read access.
20 23 */
21 24 @Singleton
22 25 public class IssueTools
@@ -25,6 +28,9 @@
25 28 IssueService issues;
26 29
27 30 @Inject
31 + IssueCommentService comments;
32 +
33 + @Inject
28 34 McpRepoAccess access;
29 35
30 36 @Inject
@@ -83,6 +89,28 @@
83 89 return "Deleted issue #" + number;
84 90 }
85 91
92 + @Tool(description = "List the discussion comments of an issue.")
93 + public List<ApiModels.IssueCommentView> listIssueComments(@ToolArg(description = "Owner username") String owner,
94 + @ToolArg(description = "Repository name") String name, @ToolArg(description = "Issue number") int number)
95 + {
96 + Repository repo = access.requireReadable(principal.orNull(), owner, name);
97 + Issue issue = requireIssue(repo, number);
98 + return comments.list(issue).stream().map(ApiModels.IssueCommentView::of).toList();
99 + }
100 +
101 + @Tool(description = "Add a discussion comment to an issue. Requires a token; any reader may comment.")
102 + @Transactional
103 + public ApiModels.IssueCommentView addIssueComment(@ToolArg(description = "Owner username") String owner,
104 + @ToolArg(description = "Repository name") String name, @ToolArg(description = "Issue number") int number,
105 + @ToolArg(description = "Comment body") String body)
106 + {
107 + User user = principal.require();
108 + Repository repo = access.requireReadable(user, owner, name);
109 + Issue issue = requireIssue(repo, number);
110 + IssueComment comment = comments.add(user, issue, body);
111 + return ApiModels.IssueCommentView.of(comment);
112 + }
113 +
86 114 private Issue requireIssue(Repository repo, int number)
87 115 {
88 116 return issues.find(repo, number)
MODIFY src/test/java/de/workaround/mcp/McpToolsTest.java +39 -0
diff --git a/src/test/java/de/workaround/mcp/McpToolsTest.java b/src/test/java/de/workaround/mcp/McpToolsTest.java
index a1fd7a3..7c724c1 100644
--- a/src/test/java/de/workaround/mcp/McpToolsTest.java
+++ b/src/test/java/de/workaround/mcp/McpToolsTest.java
@@ -6,7 +6,9 @@
6 6 import org.junit.jupiter.api.Test;
7 7
8 8 import de.workaround.git.GitRepositoryService;
9 +import de.workaround.git.IssueService;
9 10 import de.workaround.http.AccessTokenService;
11 +import de.workaround.model.Issue;
10 12 import de.workaround.model.Repository;
11 13 import de.workaround.model.User;
12 14 import io.quarkiverse.mcp.server.test.McpAssured;
@@ -30,6 +32,9 @@
30 32 AccessTokenService tokenService;
31 33
32 34 @Inject
35 + IssueService issueService;
36 +
37 + @Inject
33 38 User.Repo userRepo;
34 39
35 40 @Test
@@ -120,6 +125,40 @@
120 125 .thenAssertResults();
121 126 }
122 127
128 + @Test
129 + void addAndListIssueCommentTools()
130 + {
131 + User owner = persistUser("mcp-icmt-" + shortId());
132 + Repository repo = service.create(owner, "mcpissuecomments", Repository.Visibility.PUBLIC, null);
133 + int issueNumber = createIssue(owner, repo, "needs discussion");
134 + String token = tokenService.create(owner, "mcp").plaintext();
135 +
136 + McpStreamableTestClient client = McpAssured.newStreamableClient()
137 + .setMcpPath("/mcp")
138 + .setAdditionalHeaders(json -> MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token))
139 + .build().connect();
140 +
141 + client.when()
142 + .toolsCall("addIssueComment", Map.of("owner", owner.username, "name", "mcpissuecomments",
143 + "number", issueNumber, "body", "first thought"), response -> {
144 + assertFalse(response.isError());
145 + assertTrue(response.firstContent().asText().text().contains("first thought"));
146 + })
147 + .toolsCall("listIssueComments", Map.of("owner", owner.username, "name", "mcpissuecomments",
148 + "number", issueNumber), response -> {
149 + assertFalse(response.isError());
150 + assertTrue(response.firstContent().asText().text().contains("first thought"));
151 + })
152 + .thenAssertResults();
153 + }
154 +
155 + @Transactional
156 + int createIssue(User owner, Repository repo, String title)
157 + {
158 + Issue issue = issueService.create(owner, repo, title, null);
159 + return issue.number;
160 + }
161 +
123 162 private static String shortId()
124 163 {
125 164 return UUID.randomUUID().toString().substring(0, 8);

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog