@@ -214,8 +214,10 @@ Options are:
2142142. get_comments - Get issue comments.
2152153. get_sub_issues - Get sub-issues of the issue.
2162164. get_labels - Get labels assigned to the issue.
217+ 5. get_dependencies_blocked_by - Get dependencies that block the issue.
218+ 6. get_dependencies_blocking - Get dependencies the issue is blocking.
217219` ,
218- Enum : []any {"get" , "get_comments" , "get_sub_issues" , "get_labels" },
220+ Enum : []any {"get" , "get_comments" , "get_sub_issues" , "get_labels" , "get_dependencies_blocked_by" , "get_dependencies_blocking" },
219221 },
220222 "owner" : {
221223 Type : "string" ,
@@ -293,6 +295,12 @@ Options are:
293295 case "get_labels" :
294296 result , err := GetIssueLabels (ctx , gqlClient , owner , repo , issueNumber )
295297 return result , nil , err
298+ case "get_dependencies_blocked_by" :
299+ result , err := GetIssueDependenciesBlockedBy (ctx , client , deps , owner , repo , issueNumber , pagination )
300+ return result , nil , err
301+ case "get_dependencies_blocking" :
302+ result , err := GetIssueDependenciesBlocking (ctx , client , deps , owner , repo , issueNumber , pagination )
303+ return result , nil , err
296304 default :
297305 return utils .NewToolResultError (fmt .Sprintf ("unknown method: %s" , method )), nil , nil
298306 }
@@ -477,6 +485,86 @@ func GetSubIssues(ctx context.Context, client *github.Client, deps ToolDependenc
477485 return utils .NewToolResultText (string (r )), nil
478486}
479487
488+ func GetIssueDependenciesBlockedBy (ctx context.Context , client * github.Client , deps ToolDependencies , owner string , repo string , issueNumber int , pagination PaginationParams ) (* mcp.CallToolResult , error ) {
489+ return getIssueDependencies (ctx , client , deps , owner , repo , issueNumber , "blocked_by" , pagination )
490+ }
491+
492+ func GetIssueDependenciesBlocking (ctx context.Context , client * github.Client , deps ToolDependencies , owner string , repo string , issueNumber int , pagination PaginationParams ) (* mcp.CallToolResult , error ) {
493+ return getIssueDependencies (ctx , client , deps , owner , repo , issueNumber , "blocking" , pagination )
494+ }
495+
496+ func getIssueDependencies (ctx context.Context , client * github.Client , deps ToolDependencies , owner string , repo string , issueNumber int , relation string , pagination PaginationParams ) (* mcp.CallToolResult , error ) {
497+ cache , err := deps .GetRepoAccessCache (ctx )
498+ if err != nil {
499+ return nil , fmt .Errorf ("failed to get repo access cache: %w" , err )
500+ }
501+ flags := deps .GetFlags (ctx )
502+
503+ path := fmt .Sprintf ("repos/%s/%s/issues/%d/dependencies/%s" , owner , repo , issueNumber , relation )
504+ req , err := client .NewRequest (http .MethodGet , path , nil )
505+ if err != nil {
506+ return nil , fmt .Errorf ("failed to create dependencies request: %w" , err )
507+ }
508+
509+ q := req .URL .Query ()
510+ if pagination .Page != 0 {
511+ q .Set ("page" , fmt .Sprintf ("%d" , pagination .Page ))
512+ }
513+ if pagination .PerPage != 0 {
514+ q .Set ("per_page" , fmt .Sprintf ("%d" , pagination .PerPage ))
515+ }
516+ req .URL .RawQuery = q .Encode ()
517+
518+ var issues []* github.Issue
519+ resp , err := client .Do (ctx , req , & issues )
520+ if err != nil {
521+ return ghErrors .NewGitHubAPIErrorResponse (ctx , "failed to list issue dependencies" , resp , err ), nil
522+ }
523+ defer func () { _ = resp .Body .Close () }()
524+
525+ if resp .StatusCode != http .StatusOK {
526+ body , err := io .ReadAll (resp .Body )
527+ if err != nil {
528+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
529+ }
530+ return ghErrors .NewGitHubAPIStatusErrorResponse (ctx , "failed to list issue dependencies" , resp , body ), nil
531+ }
532+
533+ if flags .LockdownMode {
534+ if cache == nil {
535+ return nil , fmt .Errorf ("lockdown cache is not configured" )
536+ }
537+ filteredIssues := make ([]* github.Issue , 0 , len (issues ))
538+ for _ , issue := range issues {
539+ user := issue .GetUser ()
540+ if user == nil {
541+ continue
542+ }
543+ login := user .GetLogin ()
544+ if login == "" {
545+ continue
546+ }
547+ isSafeContent , err := cache .IsSafeContent (ctx , login , owner , repo )
548+ if err != nil {
549+ return utils .NewToolResultError (fmt .Sprintf ("failed to check lockdown mode: %v" , err )), nil
550+ }
551+ if isSafeContent {
552+ filteredIssues = append (filteredIssues , issue )
553+ }
554+ }
555+ issues = filteredIssues
556+ }
557+
558+ minimalIssues := make ([]MinimalIssue , 0 , len (issues ))
559+ for _ , issue := range issues {
560+ if issue != nil {
561+ minimalIssues = append (minimalIssues , convertToMinimalIssue (issue ))
562+ }
563+ }
564+
565+ return MarshalledTextResult (minimalIssues ), nil
566+ }
567+
480568func GetIssueLabels (ctx context.Context , client * githubv4.Client , owner string , repo string , issueNumber int ) (* mcp.CallToolResult , error ) {
481569 // Get current labels on the issue using GraphQL
482570 var query struct {
@@ -675,6 +763,140 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool
675763 })
676764}
677765
766+ // IssueDependencyWrite creates a tool to add or remove dependency edges for an issue.
767+ func IssueDependencyWrite (t translations.TranslationHelperFunc ) inventory.ServerTool {
768+ return NewTool (
769+ ToolsetMetadataIssues ,
770+ mcp.Tool {
771+ Name : "issue_dependency_write" ,
772+ Description : t ("TOOL_ISSUE_DEPENDENCY_WRITE_DESCRIPTION" , "Add or remove issue dependencies in a GitHub repository." ),
773+ Annotations : & mcp.ToolAnnotations {
774+ Title : t ("TOOL_ISSUE_DEPENDENCY_WRITE_USER_TITLE" , "Change issue dependency" ),
775+ ReadOnlyHint : false ,
776+ },
777+ InputSchema : & jsonschema.Schema {
778+ Type : "object" ,
779+ Properties : map [string ]* jsonschema.Schema {
780+ "method" : {
781+ Type : "string" ,
782+ Description : `The action to perform on an issue dependency.
783+ Options are:
784+ - 'add_blocked_by' - add a blocking dependency to the issue.
785+ - 'remove_blocked_by' - remove a blocking dependency from the issue.
786+ ` ,
787+ Enum : []any {"add_blocked_by" , "remove_blocked_by" },
788+ },
789+ "owner" : {
790+ Type : "string" ,
791+ Description : "Repository owner" ,
792+ },
793+ "repo" : {
794+ Type : "string" ,
795+ Description : "Repository name" ,
796+ },
797+ "issue_number" : {
798+ Type : "number" ,
799+ Description : "The issue number to modify" ,
800+ },
801+ "issue_id" : {
802+ Type : "number" ,
803+ Description : "The ID of the related issue to add or remove as a blocking dependency" ,
804+ },
805+ },
806+ Required : []string {"method" , "owner" , "repo" , "issue_number" , "issue_id" },
807+ },
808+ },
809+ []scopes.Scope {scopes .Repo },
810+ func (ctx context.Context , deps ToolDependencies , _ * mcp.CallToolRequest , args map [string ]any ) (* mcp.CallToolResult , any , error ) {
811+ method , err := RequiredParam [string ](args , "method" )
812+ if err != nil {
813+ return utils .NewToolResultError (err .Error ()), nil , nil
814+ }
815+ owner , err := RequiredParam [string ](args , "owner" )
816+ if err != nil {
817+ return utils .NewToolResultError (err .Error ()), nil , nil
818+ }
819+ repo , err := RequiredParam [string ](args , "repo" )
820+ if err != nil {
821+ return utils .NewToolResultError (err .Error ()), nil , nil
822+ }
823+ issueNumber , err := RequiredInt (args , "issue_number" )
824+ if err != nil {
825+ return utils .NewToolResultError (err .Error ()), nil , nil
826+ }
827+ issueID , err := RequiredInt (args , "issue_id" )
828+ if err != nil {
829+ return utils .NewToolResultError (err .Error ()), nil , nil
830+ }
831+
832+ client , err := deps .GetClient (ctx )
833+ if err != nil {
834+ return utils .NewToolResultErrorFromErr ("failed to get GitHub client" , err ), nil , nil
835+ }
836+
837+ switch strings .ToLower (method ) {
838+ case "add_blocked_by" :
839+ result , err := AddIssueDependencyBlockedBy (ctx , client , owner , repo , issueNumber , issueID )
840+ return result , nil , err
841+ case "remove_blocked_by" :
842+ result , err := RemoveIssueDependencyBlockedBy (ctx , client , owner , repo , issueNumber , issueID )
843+ return result , nil , err
844+ default :
845+ return utils .NewToolResultError (fmt .Sprintf ("unknown method: %s" , method )), nil , nil
846+ }
847+ })
848+ }
849+
850+ func AddIssueDependencyBlockedBy (ctx context.Context , client * github.Client , owner string , repo string , issueNumber int , issueID int ) (* mcp.CallToolResult , error ) {
851+ path := fmt .Sprintf ("repos/%s/%s/issues/%d/dependencies/blocked_by" , owner , repo , issueNumber )
852+ req , err := client .NewRequest (http .MethodPost , path , map [string ]int {"issue_id" : issueID })
853+ if err != nil {
854+ return nil , fmt .Errorf ("failed to create add dependency request: %w" , err )
855+ }
856+
857+ var issue github.Issue
858+ resp , err := client .Do (ctx , req , & issue )
859+ if err != nil {
860+ return ghErrors .NewGitHubAPIErrorResponse (ctx , "failed to add issue dependency" , resp , err ), nil
861+ }
862+ defer func () { _ = resp .Body .Close () }()
863+
864+ if resp .StatusCode != http .StatusCreated {
865+ body , err := io .ReadAll (resp .Body )
866+ if err != nil {
867+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
868+ }
869+ return ghErrors .NewGitHubAPIStatusErrorResponse (ctx , "failed to add issue dependency" , resp , body ), nil
870+ }
871+
872+ return MarshalledTextResult (convertToMinimalIssue (& issue )), nil
873+ }
874+
875+ func RemoveIssueDependencyBlockedBy (ctx context.Context , client * github.Client , owner string , repo string , issueNumber int , issueID int ) (* mcp.CallToolResult , error ) {
876+ path := fmt .Sprintf ("repos/%s/%s/issues/%d/dependencies/blocked_by/%d" , owner , repo , issueNumber , issueID )
877+ req , err := client .NewRequest (http .MethodDelete , path , nil )
878+ if err != nil {
879+ return nil , fmt .Errorf ("failed to create remove dependency request: %w" , err )
880+ }
881+
882+ var issue github.Issue
883+ resp , err := client .Do (ctx , req , & issue )
884+ if err != nil {
885+ return ghErrors .NewGitHubAPIErrorResponse (ctx , "failed to remove issue dependency" , resp , err ), nil
886+ }
887+ defer func () { _ = resp .Body .Close () }()
888+
889+ if resp .StatusCode != http .StatusOK {
890+ body , err := io .ReadAll (resp .Body )
891+ if err != nil {
892+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
893+ }
894+ return ghErrors .NewGitHubAPIStatusErrorResponse (ctx , "failed to remove issue dependency" , resp , body ), nil
895+ }
896+
897+ return MarshalledTextResult (convertToMinimalIssue (& issue )), nil
898+ }
899+
678900// SubIssueWrite creates a tool to add a sub-issue to a parent issue.
679901func SubIssueWrite (t translations.TranslationHelperFunc ) inventory.ServerTool {
680902 st := NewTool (
0 commit comments