@@ -683,6 +683,218 @@ func GetIssueComments(getClient GetClientFn, t translations.TranslationHelperFun
683683 }
684684}
685685
686+ // GetIssueTimeline creates a tool to get timeline for a GitHub issue.
687+ func GetIssueTimeline (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
688+ return mcp .NewTool ("get_issue_timeline" ,
689+ mcp .WithDescription (t ("TOOL_GET_ISSUE_TIMELINE_DESCRIPTION" , "Get timeline for a GitHub issue" )),
690+ mcp .WithString ("owner" ,
691+ mcp .Required (),
692+ mcp .Description ("Repository owner" ),
693+ ),
694+ mcp .WithString ("repo" ,
695+ mcp .Required (),
696+ mcp .Description ("Repository name" ),
697+ ),
698+ mcp .WithNumber ("issue_number" ,
699+ mcp .Required (),
700+ mcp .Description ("Issue number" ),
701+ ),
702+ mcp .WithNumber ("page" ,
703+ mcp .Description ("Page number" ),
704+ ),
705+ mcp .WithNumber ("per_page" ,
706+ mcp .Description ("Number of records per page" ),
707+ ),
708+ ),
709+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
710+ owner , err := requiredParam [string ](request , "owner" )
711+ if err != nil {
712+ return mcp .NewToolResultError (err .Error ()), nil
713+ }
714+ repo , err := requiredParam [string ](request , "repo" )
715+ if err != nil {
716+ return mcp .NewToolResultError (err .Error ()), nil
717+ }
718+ issueNumber , err := RequiredInt (request , "issue_number" )
719+ if err != nil {
720+ return mcp .NewToolResultError (err .Error ()), nil
721+ }
722+ page , err := OptionalIntParamWithDefault (request , "page" , 1 )
723+ if err != nil {
724+ return mcp .NewToolResultError (err .Error ()), nil
725+ }
726+ perPage , err := OptionalIntParamWithDefault (request , "per_page" , 30 )
727+ if err != nil {
728+ return mcp .NewToolResultError (err .Error ()), nil
729+ }
730+
731+ opts := & github.ListOptions {
732+ Page : page ,
733+ PerPage : perPage ,
734+ }
735+
736+ client , err := getClient (ctx )
737+ if err != nil {
738+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
739+ }
740+ events , resp , err := client .Issues .ListIssueTimeline (ctx , owner , repo , issueNumber , opts )
741+ if err != nil {
742+ return nil , fmt .Errorf ("failed to get issue timeline: %w" , err )
743+ }
744+ defer func () { _ = resp .Body .Close () }()
745+
746+ if resp .StatusCode != http .StatusOK {
747+ body , err := io .ReadAll (resp .Body )
748+ if err != nil {
749+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
750+ }
751+ return mcp .NewToolResultError (fmt .Sprintf ("failed to get issue timeline: %s" , string (body ))), nil
752+ }
753+
754+ r , err := json .Marshal (events )
755+ if err != nil {
756+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
757+ }
758+
759+ return mcp .NewToolResultText (string (r )), nil
760+ }
761+ }
762+
763+ // GetIssueEvents creates a tool to get events for a GitHub issue.
764+ func GetIssueEvents (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
765+ return mcp .NewTool ("get_issue_events" ,
766+ mcp .WithDescription (t ("TOOL_GET_ISSUE_EVENTS_DESCRIPTION" , "Get list of events for a GitHub issue" )),
767+ mcp .WithString ("owner" ,
768+ mcp .Required (),
769+ mcp .Description ("Repository owner" ),
770+ ),
771+ mcp .WithString ("repo" ,
772+ mcp .Required (),
773+ mcp .Description ("Repository name" ),
774+ ),
775+ mcp .WithNumber ("issue_number" ,
776+ mcp .Required (),
777+ mcp .Description ("Issue number" ),
778+ ),
779+ mcp .WithNumber ("page" ,
780+ mcp .Description ("Page number" ),
781+ ),
782+ mcp .WithNumber ("per_page" ,
783+ mcp .Description ("Number of records per page" ),
784+ ),
785+ ),
786+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
787+ owner , err := requiredParam [string ](request , "owner" )
788+ if err != nil {
789+ return mcp .NewToolResultError (err .Error ()), nil
790+ }
791+ repo , err := requiredParam [string ](request , "repo" )
792+ if err != nil {
793+ return mcp .NewToolResultError (err .Error ()), nil
794+ }
795+ issueNumber , err := RequiredInt (request , "issue_number" )
796+ if err != nil {
797+ return mcp .NewToolResultError (err .Error ()), nil
798+ }
799+ page , err := OptionalIntParamWithDefault (request , "page" , 1 )
800+ if err != nil {
801+ return mcp .NewToolResultError (err .Error ()), nil
802+ }
803+ perPage , err := OptionalIntParamWithDefault (request , "per_page" , 30 )
804+ if err != nil {
805+ return mcp .NewToolResultError (err .Error ()), nil
806+ }
807+
808+ opts := & github.ListOptions {
809+ Page : page ,
810+ PerPage : perPage ,
811+ }
812+
813+ client , err := getClient (ctx )
814+ if err != nil {
815+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
816+ }
817+ events , resp , err := client .Issues .ListIssueEvents (ctx , owner , repo , issueNumber , opts )
818+ if err != nil {
819+ return nil , fmt .Errorf ("failed to get issue events: %w" , err )
820+ }
821+ defer func () { _ = resp .Body .Close () }()
822+
823+ if resp .StatusCode != http .StatusOK {
824+ body , err := io .ReadAll (resp .Body )
825+ if err != nil {
826+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
827+ }
828+ return mcp .NewToolResultError (fmt .Sprintf ("failed to get issue events: %s" , string (body ))), nil
829+ }
830+
831+ r , err := json .Marshal (events )
832+ if err != nil {
833+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
834+ }
835+
836+ return mcp .NewToolResultText (string (r )), nil
837+ }
838+ }
839+
840+ // GetIssueEvent creates a tool to get an event for a GitHub issue.
841+ func GetIssueEvent (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
842+ return mcp .NewTool ("get_issue_event" ,
843+ mcp .WithDescription (t ("TOOL_GET_ISSUE_EVENT_DESCRIPTION" , "Get single event for a GitHub issue" )),
844+ mcp .WithString ("owner" ,
845+ mcp .Required (),
846+ mcp .Description ("Repository owner" ),
847+ ),
848+ mcp .WithString ("repo" ,
849+ mcp .Required (),
850+ mcp .Description ("Repository name" ),
851+ ),
852+ mcp .WithNumber ("event_id" ,
853+ mcp .Required (),
854+ mcp .Description ("Event ID" ),
855+ ),
856+ ),
857+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
858+ owner , err := requiredParam [string ](request , "owner" )
859+ if err != nil {
860+ return mcp .NewToolResultError (err .Error ()), nil
861+ }
862+ repo , err := requiredParam [string ](request , "repo" )
863+ if err != nil {
864+ return mcp .NewToolResultError (err .Error ()), nil
865+ }
866+ eventID , err := RequiredInt64 (request , "event_id" )
867+ if err != nil {
868+ return mcp .NewToolResultError (err .Error ()), nil
869+ }
870+
871+ client , err := getClient (ctx )
872+ if err != nil {
873+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
874+ }
875+ event , resp , err := client .Issues .GetEvent (ctx , owner , repo , eventID )
876+ if err != nil {
877+ return nil , fmt .Errorf ("failed to get issue event: %w" , err )
878+ }
879+ defer func () { _ = resp .Body .Close () }()
880+
881+ if resp .StatusCode != http .StatusOK {
882+ body , err := io .ReadAll (resp .Body )
883+ if err != nil {
884+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
885+ }
886+ return mcp .NewToolResultError (fmt .Sprintf ("failed to get issue event: %s" , string (body ))), nil
887+ }
888+
889+ r , err := json .Marshal (event )
890+ if err != nil {
891+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
892+ }
893+
894+ return mcp .NewToolResultText (string (r )), nil
895+ }
896+ }
897+
686898// parseISOTimestamp parses an ISO 8601 timestamp string into a time.Time object.
687899// Returns the parsed time or an error if parsing fails.
688900// Example formats supported: "2023-01-15T14:30:00Z", "2023-01-15"
0 commit comments