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