Skip to content

Commit 0d6d026

Browse files
committed
Create 'add sub-issue' tool
1 parent 87a4770 commit 0d6d026

File tree

3 files changed

+406
-0
lines changed

3 files changed

+406
-0
lines changed

pkg/github/issues.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,118 @@ func AddIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc
153153
}
154154
}
155155

156+
// AddSubIssue creates a tool to add a sub-issue to a parent issue.
157+
func AddSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
158+
return mcp.NewTool("add_sub_issue",
159+
mcp.WithDescription(t("TOOL_ADD_SUB_ISSUE_DESCRIPTION", "Add a sub-issue to a parent issue in a GitHub repository.")),
160+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
161+
Title: t("TOOL_ADD_SUB_ISSUE_USER_TITLE", "Add sub-issue"),
162+
ReadOnlyHint: toBoolPtr(false),
163+
}),
164+
mcp.WithString("owner",
165+
mcp.Required(),
166+
mcp.Description("Repository owner"),
167+
),
168+
mcp.WithString("repo",
169+
mcp.Required(),
170+
mcp.Description("Repository name"),
171+
),
172+
mcp.WithNumber("issue_number",
173+
mcp.Required(),
174+
mcp.Description("The number of the parent issue"),
175+
),
176+
mcp.WithNumber("sub_issue_id",
177+
mcp.Required(),
178+
mcp.Description("The ID of the sub-issue to add"),
179+
),
180+
mcp.WithBoolean("replace_parent",
181+
mcp.Description("When true, replaces the sub-issue's current parent issue"),
182+
),
183+
),
184+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
185+
owner, err := requiredParam[string](request, "owner")
186+
if err != nil {
187+
return mcp.NewToolResultError(err.Error()), nil
188+
}
189+
repo, err := requiredParam[string](request, "repo")
190+
if err != nil {
191+
return mcp.NewToolResultError(err.Error()), nil
192+
}
193+
issueNumber, err := RequiredInt(request, "issue_number")
194+
if err != nil {
195+
return mcp.NewToolResultError(err.Error()), nil
196+
}
197+
subIssueID, err := RequiredInt(request, "sub_issue_id")
198+
if err != nil {
199+
return mcp.NewToolResultError(err.Error()), nil
200+
}
201+
replaceParent, err := OptionalParam[bool](request, "replace_parent")
202+
if err != nil {
203+
return mcp.NewToolResultError(err.Error()), nil
204+
}
205+
206+
client, err := getClient(ctx)
207+
if err != nil {
208+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
209+
}
210+
211+
// Create the request body
212+
requestBody := map[string]interface{}{
213+
"sub_issue_id": subIssueID,
214+
}
215+
if replaceParent {
216+
requestBody["replace_parent"] = replaceParent
217+
}
218+
219+
// Since the go-github library might not have sub-issues support yet,
220+
// we'll make a direct HTTP request using the client's HTTP client
221+
reqBodyBytes, err := json.Marshal(requestBody)
222+
if err != nil {
223+
return nil, fmt.Errorf("failed to marshal request body: %w", err)
224+
}
225+
226+
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/issues/%d/sub_issues", owner, repo, issueNumber)
227+
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(reqBodyBytes)))
228+
if err != nil {
229+
return nil, fmt.Errorf("failed to create request: %w", err)
230+
}
231+
232+
req.Header.Set("Accept", "application/vnd.github+json")
233+
req.Header.Set("Content-Type", "application/json")
234+
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
235+
236+
// Use the same authentication as the GitHub client
237+
httpClient := client.Client()
238+
resp, err := httpClient.Do(req)
239+
if err != nil {
240+
return nil, fmt.Errorf("failed to add sub-issue: %w", err)
241+
}
242+
defer func() { _ = resp.Body.Close() }()
243+
244+
body, err := io.ReadAll(resp.Body)
245+
if err != nil {
246+
return nil, fmt.Errorf("failed to read response body: %w", err)
247+
}
248+
249+
if resp.StatusCode != http.StatusCreated {
250+
return mcp.NewToolResultError(fmt.Sprintf("failed to add sub-issue: %s", string(body))), nil
251+
}
252+
253+
// Parse and re-marshal to ensure consistent formatting
254+
var result interface{}
255+
if err := json.Unmarshal(body, &result); err != nil {
256+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
257+
}
258+
259+
r, err := json.Marshal(result)
260+
if err != nil {
261+
return nil, fmt.Errorf("failed to marshal response: %w", err)
262+
}
263+
264+
return mcp.NewToolResultText(string(r)), nil
265+
}
266+
}
267+
156268
// SearchIssues creates a tool to search for issues and pull requests.
157269
func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
158270
return mcp.NewTool("search_issues",

0 commit comments

Comments
 (0)