@@ -584,6 +584,10 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
584
584
mcp .WithBoolean ("return_content" ,
585
585
mcp .Description ("Returns actual log content instead of URLs" ),
586
586
),
587
+ mcp .WithNumber ("tail_lines" ,
588
+ mcp .Description ("Number of lines to return from the end of the log" ),
589
+ mcp .DefaultNumber (500 ),
590
+ ),
587
591
),
588
592
func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
589
593
owner , err := RequiredParam [string ](request , "owner" )
@@ -612,6 +616,14 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
612
616
if err != nil {
613
617
return mcp .NewToolResultError (err .Error ()), nil
614
618
}
619
+ tailLines , err := OptionalIntParam (request , "tail_lines" )
620
+ if err != nil {
621
+ return mcp .NewToolResultError (err .Error ()), nil
622
+ }
623
+ // Default to 500 lines if not specified
624
+ if tailLines == 0 {
625
+ tailLines = 500
626
+ }
615
627
616
628
client , err := getClient (ctx )
617
629
if err != nil {
@@ -628,18 +640,18 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
628
640
629
641
if failedOnly && runID > 0 {
630
642
// Handle failed-only mode: get logs for all failed jobs in the workflow run
631
- return handleFailedJobLogs (ctx , client , owner , repo , int64 (runID ), returnContent )
643
+ return handleFailedJobLogs (ctx , client , owner , repo , int64 (runID ), returnContent , tailLines )
632
644
} else if jobID > 0 {
633
645
// Handle single job mode
634
- return handleSingleJobLogs (ctx , client , owner , repo , int64 (jobID ), returnContent )
646
+ return handleSingleJobLogs (ctx , client , owner , repo , int64 (jobID ), returnContent , tailLines )
635
647
}
636
648
637
649
return mcp .NewToolResultError ("Either job_id must be provided for single job logs, or run_id with failed_only=true for failed job logs" ), nil
638
650
}
639
651
}
640
652
641
653
// handleFailedJobLogs gets logs for all failed jobs in a workflow run
642
- func handleFailedJobLogs (ctx context.Context , client * github.Client , owner , repo string , runID int64 , returnContent bool ) (* mcp.CallToolResult , error ) {
654
+ func handleFailedJobLogs (ctx context.Context , client * github.Client , owner , repo string , runID int64 , returnContent bool , tailLines int ) (* mcp.CallToolResult , error ) {
643
655
// First, get all jobs for the workflow run
644
656
jobs , resp , err := client .Actions .ListWorkflowJobs (ctx , owner , repo , runID , & github.ListWorkflowJobsOptions {
645
657
Filter : "latest" ,
@@ -671,7 +683,7 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
671
683
// Collect logs for all failed jobs
672
684
var logResults []map [string ]any
673
685
for _ , job := range failedJobs {
674
- jobResult , resp , err := getJobLogData (ctx , client , owner , repo , job .GetID (), job .GetName (), returnContent )
686
+ jobResult , resp , err := getJobLogData (ctx , client , owner , repo , job .GetID (), job .GetName (), returnContent , tailLines )
675
687
if err != nil {
676
688
// Continue with other jobs even if one fails
677
689
jobResult = map [string ]any {
@@ -704,8 +716,8 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
704
716
}
705
717
706
718
// handleSingleJobLogs gets logs for a single job
707
- func handleSingleJobLogs (ctx context.Context , client * github.Client , owner , repo string , jobID int64 , returnContent bool ) (* mcp.CallToolResult , error ) {
708
- jobResult , resp , err := getJobLogData (ctx , client , owner , repo , jobID , "" , returnContent )
719
+ func handleSingleJobLogs (ctx context.Context , client * github.Client , owner , repo string , jobID int64 , returnContent bool , tailLines int ) (* mcp.CallToolResult , error ) {
720
+ jobResult , resp , err := getJobLogData (ctx , client , owner , repo , jobID , "" , returnContent , tailLines )
709
721
if err != nil {
710
722
return ghErrors .NewGitHubAPIErrorResponse (ctx , "failed to get job logs" , resp , err ), nil
711
723
}
@@ -719,7 +731,7 @@ func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo
719
731
}
720
732
721
733
// getJobLogData retrieves log data for a single job, either as URL or content
722
- func getJobLogData (ctx context.Context , client * github.Client , owner , repo string , jobID int64 , jobName string , returnContent bool ) (map [string ]any , * github.Response , error ) {
734
+ func getJobLogData (ctx context.Context , client * github.Client , owner , repo string , jobID int64 , jobName string , returnContent bool , tailLines int ) (map [string ]any , * github.Response , error ) {
723
735
// Get the download URL for the job logs
724
736
url , resp , err := client .Actions .GetWorkflowJobLogs (ctx , owner , repo , jobID , 1 )
725
737
if err != nil {
@@ -736,7 +748,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
736
748
737
749
if returnContent {
738
750
// Download and return the actual log content
739
- content , httpResp , err := downloadLogContent (url .String ()) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
751
+ content , originalLength , httpResp , err := downloadLogContent (url .String (), tailLines ) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
740
752
if err != nil {
741
753
// To keep the return value consistent wrap the response as a GitHub Response
742
754
ghRes := & github.Response {
@@ -746,6 +758,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
746
758
}
747
759
result ["logs_content" ] = content
748
760
result ["message" ] = "Job logs content retrieved successfully"
761
+ result ["original_length" ] = originalLength
749
762
} else {
750
763
// Return just the URL
751
764
result ["logs_url" ] = url .String ()
@@ -757,25 +770,46 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
757
770
}
758
771
759
772
// downloadLogContent downloads the actual log content from a GitHub logs URL
760
- func downloadLogContent (logURL string ) (string , * http.Response , error ) {
773
+ func downloadLogContent (logURL string , tailLines int ) (string , int , * http.Response , error ) {
761
774
httpResp , err := http .Get (logURL ) //nolint:gosec // URLs are provided by GitHub API and are safe
762
775
if err != nil {
763
- return "" , httpResp , fmt .Errorf ("failed to download logs: %w" , err )
776
+ return "" , 0 , httpResp , fmt .Errorf ("failed to download logs: %w" , err )
764
777
}
765
778
defer func () { _ = httpResp .Body .Close () }()
766
779
767
780
if httpResp .StatusCode != http .StatusOK {
768
- return "" , httpResp , fmt .Errorf ("failed to download logs: HTTP %d" , httpResp .StatusCode )
781
+ return "" , 0 , httpResp , fmt .Errorf ("failed to download logs: HTTP %d" , httpResp .StatusCode )
769
782
}
770
783
771
784
content , err := io .ReadAll (httpResp .Body )
772
785
if err != nil {
773
- return "" , httpResp , fmt .Errorf ("failed to read log content: %w" , err )
786
+ return "" , 0 , httpResp , fmt .Errorf ("failed to read log content: %w" , err )
774
787
}
775
788
776
789
// Clean up and format the log content for better readability
777
790
logContent := strings .TrimSpace (string (content ))
778
- return logContent , httpResp , nil
791
+
792
+ trimmedContent , lineCount := trimContent (logContent , tailLines )
793
+ return trimmedContent , lineCount , httpResp , nil
794
+ }
795
+
796
+ // trimContent trims the content to a maximum length and returns the trimmed content and an original length
797
+ func trimContent (content string , tailLines int ) (string , int ) {
798
+ // Truncate to tail_lines if specified
799
+ lineCount := 0
800
+ if tailLines > 0 {
801
+
802
+ // Count backwards to find the nth newline from the end
803
+ for i := len (content ) - 1 ; i >= 0 && lineCount < tailLines ; i -- {
804
+ if content [i ] == '\n' {
805
+ lineCount ++
806
+ if lineCount == tailLines {
807
+ content = content [i + 1 :]
808
+ }
809
+ }
810
+ }
811
+ }
812
+ return content , lineCount
779
813
}
780
814
781
815
// RerunWorkflowRun creates a tool to re-run an entire workflow run
0 commit comments