@@ -584,6 +584,10 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
584584 mcp .WithBoolean ("return_content" ,
585585 mcp .Description ("Returns actual log content instead of URLs" ),
586586 ),
587+ mcp .WithNumber ("tail_lines" ,
588+ mcp .Description ("Number of lines to return from the end of the log" ),
589+ mcp .DefaultNumber (500 ),
590+ ),
587591 ),
588592 func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
589593 owner , err := RequiredParam [string ](request , "owner" )
@@ -612,6 +616,14 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
612616 if err != nil {
613617 return mcp .NewToolResultError (err .Error ()), nil
614618 }
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+ }
615627
616628 client , err := getClient (ctx )
617629 if err != nil {
@@ -628,18 +640,18 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
628640
629641 if failedOnly && runID > 0 {
630642 // 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 )
632644 } else if jobID > 0 {
633645 // 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 )
635647 }
636648
637649 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
638650 }
639651}
640652
641653// 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 ) {
643655 // First, get all jobs for the workflow run
644656 jobs , resp , err := client .Actions .ListWorkflowJobs (ctx , owner , repo , runID , & github.ListWorkflowJobsOptions {
645657 Filter : "latest" ,
@@ -671,7 +683,7 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
671683 // Collect logs for all failed jobs
672684 var logResults []map [string ]any
673685 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 )
675687 if err != nil {
676688 // Continue with other jobs even if one fails
677689 jobResult = map [string ]any {
@@ -704,8 +716,8 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
704716}
705717
706718// 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 )
709721 if err != nil {
710722 return ghErrors .NewGitHubAPIErrorResponse (ctx , "failed to get job logs" , resp , err ), nil
711723 }
@@ -719,7 +731,7 @@ func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo
719731}
720732
721733// 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 ) {
723735 // Get the download URL for the job logs
724736 url , resp , err := client .Actions .GetWorkflowJobLogs (ctx , owner , repo , jobID , 1 )
725737 if err != nil {
@@ -736,7 +748,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
736748
737749 if returnContent {
738750 // 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
740752 if err != nil {
741753 // To keep the return value consistent wrap the response as a GitHub Response
742754 ghRes := & github.Response {
@@ -746,6 +758,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
746758 }
747759 result ["logs_content" ] = content
748760 result ["message" ] = "Job logs content retrieved successfully"
761+ result ["original_length" ] = originalLength
749762 } else {
750763 // Return just the URL
751764 result ["logs_url" ] = url .String ()
@@ -757,25 +770,46 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
757770}
758771
759772// 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 ) {
761774 httpResp , err := http .Get (logURL ) //nolint:gosec // URLs are provided by GitHub API and are safe
762775 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 )
764777 }
765778 defer func () { _ = httpResp .Body .Close () }()
766779
767780 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 )
769782 }
770783
771784 content , err := io .ReadAll (httpResp .Body )
772785 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 )
774787 }
775788
776789 // Clean up and format the log content for better readability
777790 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
779813}
780814
781815// RerunWorkflowRun creates a tool to re-run an entire workflow run
0 commit comments