feat(issue): add issue dependency methods #3
@@ -66,6 +66,60 @@ func setIssueBlockingFn(ctx context.Context, args map[string]any, block bool) (*
|
|||||||
return to.TextResult(slimIssue(issue))
|
return to.TextResult(slimIssue(issue))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listIssueDependenciesFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||||
|
owner, repo, index, err := issueRef(args)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
page, pageSize := params.GetPagination(args, 30)
|
||||||
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||||
|
}
|
||||||
|
listOpt := gitea_sdk.ListOptions{Page: page, PageSize: pageSize}
|
||||||
|
issues, _, err := client.Issues.ListIssueDependencies(ctx, owner, repo, index, gitea_sdk.ListIssueDependenciesOptions{
|
||||||
|
ListOptions: listOpt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("list %v/%v/issue/%v/dependencies err: %v", owner, repo, index, err))
|
||||||
|
}
|
||||||
|
return to.TextResult(slimIssues(issues))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addIssueDependencyFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||||
|
return setIssueDependencyFn(ctx, args, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeIssueDependencyFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||||
|
return setIssueDependencyFn(ctx, args, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIssueDependencyFn(ctx context.Context, args map[string]any, add bool) (*mcp.CallToolResult, error) {
|
||||||
|
owner, repo, index, err := issueRef(args)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
blocking, err := params.GetIndex(args, "blocking_issue")
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
meta := gitea_sdk.IssueMeta{Index: blocking}
|
||||||
|
var issue *gitea_sdk.Issue
|
||||||
|
if add {
|
||||||
|
issue, _, err = client.Issues.CreateIssueDependency(ctx, owner, repo, index, meta)
|
||||||
|
} else {
|
||||||
|
issue, _, err = client.Issues.RemoveIssueDependency(ctx, owner, repo, index, meta)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("update %v/%v/issue/%v/dependencies err: %v", owner, repo, index, err))
|
||||||
|
}
|
||||||
|
return to.TextResult(slimIssue(issue))
|
||||||
|
}
|
||||||
|
|
||||||
func issueRef(args map[string]any) (owner, repo string, index int64, err error) {
|
func issueRef(args map[string]any) (owner, repo string, index int64, err error) {
|
||||||
if owner, err = params.GetString(args, "owner"); err != nil {
|
if owner, err = params.GetString(args, "owner"); err != nil {
|
||||||
return "", "", 0, err
|
return "", "", 0, err
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ func Test_issueBlocking(t *testing.T) {
|
|||||||
repo = "demo"
|
repo = "demo"
|
||||||
)
|
)
|
||||||
blocksPath := fmt.Sprintf("/api/v1/repos/%s/%s/issues/42/blocks", owner, repo)
|
blocksPath := fmt.Sprintf("/api/v1/repos/%s/%s/issues/42/blocks", owner, repo)
|
||||||
|
depsPath := fmt.Sprintf("/api/v1/repos/%s/%s/issues/42/dependencies", owner, repo)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fn func(context.Context, map[string]any) (*mcp.CallToolResult, error)
|
fn func(context.Context, map[string]any) (*mcp.CallToolResult, error)
|
||||||
args map[string]any
|
args map[string]any
|
||||||
|
wantPath string
|
||||||
wantMethod string
|
wantMethod string
|
||||||
wantQuery string
|
wantQuery string
|
||||||
wantIndex float64
|
wantIndex float64
|
||||||
@@ -34,29 +36,57 @@ func Test_issueBlocking(t *testing.T) {
|
|||||||
name: "list blocks paginates",
|
name: "list blocks paginates",
|
||||||
fn: listIssueBlocksFn,
|
fn: listIssueBlocksFn,
|
||||||
args: map[string]any{"page": float64(2), "per_page": float64(10)},
|
args: map[string]any{"page": float64(2), "per_page": float64(10)},
|
||||||
wantMethod: "GET",
|
wantPath: blocksPath,
|
||||||
|
wantMethod: http.MethodGet,
|
||||||
wantQuery: "limit=10&page=2",
|
wantQuery: "limit=10&page=2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "block issue posts index",
|
name: "block issue posts index",
|
||||||
fn: blockIssueFn,
|
fn: blockIssueFn,
|
||||||
args: map[string]any{"blocked_issue": float64(7)},
|
args: map[string]any{"blocked_issue": float64(7)},
|
||||||
wantMethod: "POST",
|
wantPath: blocksPath,
|
||||||
|
wantMethod: http.MethodPost,
|
||||||
wantIndex: 7,
|
wantIndex: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unblock issue deletes with index",
|
name: "unblock issue deletes with index",
|
||||||
fn: unblockIssueFn,
|
fn: unblockIssueFn,
|
||||||
args: map[string]any{"blocked_issue": float64(7)},
|
args: map[string]any{"blocked_issue": float64(7)},
|
||||||
wantMethod: "DELETE",
|
wantPath: blocksPath,
|
||||||
|
wantMethod: http.MethodDelete,
|
||||||
wantIndex: 7,
|
wantIndex: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list dependencies paginates",
|
||||||
|
fn: listIssueDependenciesFn,
|
||||||
|
args: map[string]any{"page": float64(3), "per_page": float64(5)},
|
||||||
|
wantPath: depsPath,
|
||||||
|
wantMethod: http.MethodGet,
|
||||||
|
wantQuery: "limit=5&page=3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add dependency posts index",
|
||||||
|
fn: addIssueDependencyFn,
|
||||||
|
args: map[string]any{"blocking_issue": float64(9)},
|
||||||
|
wantPath: depsPath,
|
||||||
|
wantMethod: http.MethodPost,
|
||||||
|
wantIndex: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove dependency deletes with index",
|
||||||
|
fn: removeIssueDependencyFn,
|
||||||
|
args: map[string]any{"blocking_issue": float64(9)},
|
||||||
|
wantPath: depsPath,
|
||||||
|
wantMethod: http.MethodDelete,
|
||||||
|
wantIndex: 9,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
gotMethod string
|
gotMethod string
|
||||||
|
gotPath string
|
||||||
gotQuery string
|
gotQuery string
|
||||||
gotBody map[string]any
|
gotBody map[string]any
|
||||||
)
|
)
|
||||||
@@ -65,8 +95,9 @@ func Test_issueBlocking(t *testing.T) {
|
|||||||
case "/api/v1/version":
|
case "/api/v1/version":
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, _ = w.Write([]byte(`{"version":"1.27.2"}`))
|
_, _ = w.Write([]byte(`{"version":"1.27.2"}`))
|
||||||
case blocksPath:
|
case blocksPath, depsPath:
|
||||||
gotMethod = r.Method
|
gotMethod = r.Method
|
||||||
|
gotPath = r.URL.Path
|
||||||
gotQuery = r.URL.RawQuery
|
gotQuery = r.URL.RawQuery
|
||||||
_ = json.NewDecoder(r.Body).Decode(&gotBody)
|
_ = json.NewDecoder(r.Body).Decode(&gotBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@@ -98,6 +129,9 @@ func Test_issueBlocking(t *testing.T) {
|
|||||||
if gotMethod != tt.wantMethod {
|
if gotMethod != tt.wantMethod {
|
||||||
t.Fatalf("method = %s, want %s", gotMethod, tt.wantMethod)
|
t.Fatalf("method = %s, want %s", gotMethod, tt.wantMethod)
|
||||||
}
|
}
|
||||||
|
if gotPath != tt.wantPath {
|
||||||
|
t.Fatalf("path = %s, want %s", gotPath, tt.wantPath)
|
||||||
|
}
|
||||||
if tt.wantQuery != "" && gotQuery != tt.wantQuery {
|
if tt.wantQuery != "" && gotQuery != tt.wantQuery {
|
||||||
t.Fatalf("query = %s, want %s", gotQuery, tt.wantQuery)
|
t.Fatalf("query = %s, want %s", gotQuery, tt.wantQuery)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ var (
|
|||||||
IssueReadToolName,
|
IssueReadToolName,
|
||||||
"Read issue: details, comments, labels, or blocking relationships.",
|
"Read issue: details, comments, labels, or blocking relationships.",
|
||||||
annotation.ReadOnly("Read issue details"),
|
annotation.ReadOnly("Read issue details"),
|
||||||
tool.String("method", tool.Required(), tool.Enum("get", "get_comments", "get_labels", "list_blocks")),
|
tool.String("method", tool.Required(), tool.Enum("get", "get_comments", "get_labels", "list_blocks", "list_dependencies")),
|
||||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||||
tool.Number("issue_number", tool.Required()),
|
tool.Number("issue_number", tool.Required()),
|
||||||
@@ -69,7 +69,7 @@ var (
|
|||||||
IssueWriteToolName,
|
IssueWriteToolName,
|
||||||
"Write issues: create, update, manage comments, labels, and blocking relationships.",
|
"Write issues: create, update, manage comments, labels, and blocking relationships.",
|
||||||
annotation.Write("Create or update issues, comments, and labels"),
|
annotation.Write("Create or update issues, comments, and labels"),
|
||||||
tool.String("method", tool.Required(), tool.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels", "block_issue", "unblock_issue")),
|
tool.String("method", tool.Required(), tool.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels", "block_issue", "unblock_issue", "add_dependency", "remove_dependency")),
|
||||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||||
tool.Number("issue_number", tool.Description("required except for 'create'")),
|
tool.Number("issue_number", tool.Description("required except for 'create'")),
|
||||||
@@ -85,6 +85,7 @@ var (
|
|||||||
tool.String("deadline", tool.Description("ISO 8601")),
|
tool.String("deadline", tool.Description("ISO 8601")),
|
||||||
tool.Boolean("remove_deadline"),
|
tool.Boolean("remove_deadline"),
|
||||||
tool.Number("blocked_issue", tool.Description("issue number that issue_number blocks (required for 'block_issue'/'unblock_issue')")),
|
tool.Number("blocked_issue", tool.Description("issue number that issue_number blocks (required for 'block_issue'/'unblock_issue')")),
|
||||||
|
tool.Number("blocking_issue", tool.Description("issue number that blocks issue_number (required for 'add_dependency'/'remove_dependency')")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,6 +118,8 @@ func issueReadFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult,
|
|||||||
return getIssueLabelsFn(ctx, args)
|
return getIssueLabelsFn(ctx, args)
|
||||||
case "list_blocks":
|
case "list_blocks":
|
||||||
return listIssueBlocksFn(ctx, args)
|
return listIssueBlocksFn(ctx, args)
|
||||||
|
case "list_dependencies":
|
||||||
|
return listIssueDependenciesFn(ctx, args)
|
||||||
default:
|
default:
|
||||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||||
}
|
}
|
||||||
@@ -148,6 +151,10 @@ func issueWriteFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult
|
|||||||
return blockIssueFn(ctx, args)
|
return blockIssueFn(ctx, args)
|
||||||
case "unblock_issue":
|
case "unblock_issue":
|
||||||
return unblockIssueFn(ctx, args)
|
return unblockIssueFn(ctx, args)
|
||||||
|
case "add_dependency":
|
||||||
|
return addIssueDependencyFn(ctx, args)
|
||||||
|
case "remove_dependency":
|
||||||
|
return removeIssueDependencyFn(ctx, args)
|
||||||
default:
|
default:
|
||||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user