Skip to content

Commit d8e9492

Browse files
committed
consolidate get logs tools
1 parent c0f99d1 commit d8e9492

File tree

3 files changed

+163
-200
lines changed

3 files changed

+163
-200
lines changed

pkg/github/actions.go

Lines changed: 107 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const (
3434
actionsActionTypeGetWorkflowRun
3535
actionsActionTypeGetWorkflowJob
3636
actionsActionTypeGetWorkflowRunUsage
37+
actionsActionTypeGetWorkflowRunLogs
38+
actionsActionTypeGetWorkflowJobLogs
3739
actionsActionTypeDownloadWorkflowArtifact
3840
actionsActionTypeRunWorkflow
3941
actionsActionTypeRerunWorkflowRun
@@ -49,6 +51,8 @@ var actionsResourceTypes = map[actionsActionType]string{
4951
actionsActionTypeGetWorkflow: "get_workflow",
5052
actionsActionTypeGetWorkflowRun: "get_workflow_run",
5153
actionsActionTypeGetWorkflowJob: "get_workflow_job",
54+
actionsActionTypeGetWorkflowRunLogs: "get_workflow_run_logs",
55+
actionsActionTypeGetWorkflowJobLogs: "get_job_logs",
5256
actionsActionTypeGetWorkflowRunUsage: "get_workflow_run_usage",
5357
actionsActionTypeDownloadWorkflowArtifact: "download_workflow_run_artifact",
5458
actionsActionTypeRunWorkflow: "run_workflow",
@@ -74,7 +78,7 @@ func actionFromString(s string) actionsActionType {
7478
return actionsActionTypeUnknown
7579
}
7680

77-
func ActionsList(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
81+
func ActionsList(getClient GetClientFn, t translations.TranslationHelperFunc, contentWindowSize int) (tool mcp.Tool, handler server.ToolHandlerFunc) {
7882
return mcp.NewTool("actions_list",
7983
mcp.WithDescription(t("TOOL_ACTIONS_LIST_DESCRIPTION", `Tools for listing GitHub Actions resources.
8084
Use this tool to list workflows in a repository, or list workflow runs, jobs, and artifacts for a specific workflow or workflow run.
@@ -247,7 +251,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an
247251
}
248252
}
249253

250-
func ActionsGet(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
254+
func ActionsGet(getClient GetClientFn, t translations.TranslationHelperFunc, contentWindowSize int) (tool mcp.Tool, handler server.ToolHandlerFunc) {
251255
return mcp.NewTool("actions_get",
252256
mcp.WithDescription(t("TOOL_ACTIONS_READ_DESCRIPTION", `Tools for reading specific GitHub Actions resources.
253257
Use this tool to get details about individual workflows, workflow runs, jobs, and artifacts, by using their unique IDs.
@@ -265,6 +269,8 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
265269
actionsActionTypeGetWorkflowJob.String(),
266270
actionsActionTypeDownloadWorkflowArtifact.String(),
267271
actionsActionTypeGetWorkflowRunUsage.String(),
272+
actionsActionTypeGetWorkflowRunLogs.String(),
273+
actionsActionTypeGetWorkflowJobLogs.String(),
268274
),
269275
),
270276
mcp.WithString("owner",
@@ -276,13 +282,26 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
276282
mcp.Description(DescriptionRepositoryName),
277283
),
278284
mcp.WithString("resource_id",
279-
mcp.Required(),
280285
mcp.Description(`The unique identifier of the resource. This will vary based on the "action" provided, so ensure you provide the correct ID:
281286
- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' action.
282-
- Provide a workflow run ID for 'get_workflow_run', 'download_workflow_run_artifact' and 'get_workflow_run_usage' actions.
287+
- Provide a workflow run ID for 'get_workflow_run', 'download_workflow_run_artifact', 'get_workflow_run_usage', and 'get_workflow_run_logs' actions.
283288
- Provide a job ID for the 'get_workflow_job' action.
289+
- Provide a workflow run ID for 'get_job_logs' action when using failed_only parameter.
284290
`),
285291
),
292+
mcp.WithBoolean("job_id",
293+
mcp.Description("The ID of the job to get logs for (only for 'get_job_logs' action)"),
294+
),
295+
mcp.WithBoolean("failed_only",
296+
mcp.Description("When true, gets logs for all failed jobs in the workflow run specified by resource_id (only for 'get_job_logs' action)"),
297+
),
298+
mcp.WithBoolean("return_content",
299+
mcp.Description("Returns actual log content instead of URLs (only for 'get_job_logs' action)"),
300+
),
301+
mcp.WithNumber("tail_lines",
302+
mcp.Description("Number of lines to return from the end of the log (only for 'get_job_logs' action)"),
303+
mcp.DefaultNumber(500),
304+
),
286305
),
287306
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
288307
owner, err := RequiredParam[string](request, "owner")
@@ -303,7 +322,36 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
303322
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
304323
}
305324

306-
resourceID, err := RequiredParam[string](request, "resource_id")
325+
var resourceID string
326+
if resourceType == actionsActionTypeGetWorkflowJobLogs {
327+
resourceID, err = OptionalParam[string](request, "resource_id")
328+
if err != nil {
329+
return mcp.NewToolResultError(err.Error()), nil
330+
}
331+
} else {
332+
resourceID, err = RequiredParam[string](request, "resource_id")
333+
if err != nil {
334+
return mcp.NewToolResultError(err.Error()), nil
335+
}
336+
}
337+
338+
// Get optional parameters for get_job_logs
339+
jobID, err := OptionalParam[int64](request, "job_id")
340+
if err != nil {
341+
return mcp.NewToolResultError(err.Error()), nil
342+
}
343+
344+
failedOnly, err := OptionalParam[bool](request, "failed_only")
345+
if err != nil {
346+
return mcp.NewToolResultError(err.Error()), nil
347+
}
348+
349+
returnContent, err := OptionalParam[bool](request, "return_content")
350+
if err != nil {
351+
return mcp.NewToolResultError(err.Error()), nil
352+
}
353+
354+
tailLines, err := OptionalParam[int](request, "tail_lines")
307355
if err != nil {
308356
return mcp.NewToolResultError(err.Error()), nil
309357
}
@@ -320,13 +368,15 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
320368
// Do nothing, we accept both a string workflow ID or filename
321369
default:
322370
if resourceID == "" {
323-
return mcp.NewToolResultError(fmt.Sprintf("missing required parameter for action %s: resource_id", actionTypeStr)), nil
324-
}
325-
326-
// For other actions, resource ID must be an integer
327-
resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64)
328-
if parseErr != nil {
329-
return mcp.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for action %s: %v", actionTypeStr, parseErr)), nil
371+
if resourceType != actionsActionTypeGetWorkflowJobLogs {
372+
return mcp.NewToolResultError(fmt.Sprintf("missing required parameter for action %s: resource_id", actionTypeStr)), nil
373+
}
374+
} else {
375+
// For other actions, resource ID must be an integer
376+
resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64)
377+
if parseErr != nil {
378+
return mcp.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for action %s: %v", actionTypeStr, parseErr)), nil
379+
}
330380
}
331381
}
332382

@@ -341,6 +391,10 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
341391
return downloadWorkflowArtifact(ctx, client, request, owner, repo, resourceIDInt)
342392
case actionsActionTypeGetWorkflowRunUsage:
343393
return getWorkflowRunUsage(ctx, client, request, owner, repo, resourceIDInt)
394+
case actionsActionTypeGetWorkflowRunLogs:
395+
return getWorkflowRunLogs(ctx, client, request, owner, repo, resourceIDInt)
396+
case actionsActionTypeGetWorkflowJobLogs:
397+
return getWorkflowJobLogs(ctx, client, request, owner, repo, resourceIDInt, jobID, returnContent, failedOnly, tailLines, contentWindowSize)
344398
case actionsActionTypeUnknown:
345399
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
346400
default:
@@ -730,163 +784,55 @@ func RunWorkflow(getClient GetClientFn, t translations.TranslationHelperFunc) (t
730784
}
731785

732786
// GetWorkflowRunLogs creates a tool to download logs for a specific workflow run
733-
func GetWorkflowRunLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
734-
return mcp.NewTool("get_workflow_run_logs",
735-
mcp.WithDescription(t("TOOL_GET_WORKFLOW_RUN_LOGS_DESCRIPTION", "Download logs for a specific workflow run (EXPENSIVE: downloads ALL logs as ZIP. Consider using get_job_logs with failed_only=true for debugging failed jobs)")),
736-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
737-
Title: t("TOOL_GET_WORKFLOW_RUN_LOGS_USER_TITLE", "Get workflow run logs"),
738-
ReadOnlyHint: ToBoolPtr(true),
739-
}),
740-
mcp.WithString("owner",
741-
mcp.Required(),
742-
mcp.Description(DescriptionRepositoryOwner),
743-
),
744-
mcp.WithString("repo",
745-
mcp.Required(),
746-
mcp.Description(DescriptionRepositoryName),
747-
),
748-
mcp.WithNumber("run_id",
749-
mcp.Required(),
750-
mcp.Description("The unique identifier of the workflow run"),
751-
),
752-
),
753-
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
754-
owner, err := RequiredParam[string](request, "owner")
755-
if err != nil {
756-
return mcp.NewToolResultError(err.Error()), nil
757-
}
758-
repo, err := RequiredParam[string](request, "repo")
759-
if err != nil {
760-
return mcp.NewToolResultError(err.Error()), nil
761-
}
762-
runIDInt, err := RequiredInt(request, "run_id")
763-
if err != nil {
764-
return mcp.NewToolResultError(err.Error()), nil
765-
}
766-
runID := int64(runIDInt)
767-
768-
client, err := getClient(ctx)
769-
if err != nil {
770-
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
771-
}
772-
773-
// Get the download URL for the logs
774-
url, resp, err := client.Actions.GetWorkflowRunLogs(ctx, owner, repo, runID, 1)
775-
if err != nil {
776-
return nil, fmt.Errorf("failed to get workflow run logs: %w", err)
777-
}
778-
defer func() { _ = resp.Body.Close() }()
787+
func getWorkflowRunLogs(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
788+
// Get the download URL for the logs
789+
url, resp, err := client.Actions.GetWorkflowRunLogs(ctx, owner, repo, runID, 1)
790+
if err != nil {
791+
return nil, fmt.Errorf("failed to get workflow run logs: %w", err)
792+
}
793+
defer func() { _ = resp.Body.Close() }()
779794

780-
// Create response with the logs URL and information
781-
result := map[string]any{
782-
"logs_url": url.String(),
783-
"message": "Workflow run logs are available for download",
784-
"note": "The logs_url provides a download link for the complete workflow run logs as a ZIP archive. You can download this archive to extract and examine individual job logs.",
785-
"warning": "This downloads ALL logs as a ZIP file which can be large and expensive. For debugging failed jobs, consider using get_job_logs with failed_only=true and run_id instead.",
786-
"optimization_tip": "Use: get_job_logs with parameters {run_id: " + fmt.Sprintf("%d", runID) + ", failed_only: true} for more efficient failed job debugging",
787-
}
795+
// Create response with the logs URL and information
796+
result := map[string]any{
797+
"logs_url": url.String(),
798+
"message": "Workflow run logs are available for download",
799+
"note": "The logs_url provides a download link for the complete workflow run logs as a ZIP archive. You can download this archive to extract and examine individual job logs.",
800+
"warning": "This downloads ALL logs as a ZIP file which can be large and expensive. For debugging failed jobs, consider using get_job_logs with failed_only=true and run_id instead.",
801+
"optimization_tip": "Use: get_job_logs with parameters {run_id: " + fmt.Sprintf("%d", runID) + ", failed_only: true} for more efficient failed job debugging",
802+
}
788803

789-
r, err := json.Marshal(result)
790-
if err != nil {
791-
return nil, fmt.Errorf("failed to marshal response: %w", err)
792-
}
804+
r, err := json.Marshal(result)
805+
if err != nil {
806+
return nil, fmt.Errorf("failed to marshal response: %w", err)
807+
}
793808

794-
return mcp.NewToolResultText(string(r)), nil
795-
}
809+
return mcp.NewToolResultText(string(r)), nil
796810
}
797811

798-
// GetJobLogs creates a tool to download logs for a specific workflow job or efficiently get all failed job logs for a workflow run
799-
func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc, contentWindowSize int) (tool mcp.Tool, handler server.ToolHandlerFunc) {
800-
return mcp.NewTool("get_job_logs",
801-
mcp.WithDescription(t("TOOL_GET_JOB_LOGS_DESCRIPTION", "Download logs for a specific workflow job or efficiently get all failed job logs for a workflow run")),
802-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
803-
Title: t("TOOL_GET_JOB_LOGS_USER_TITLE", "Get job logs"),
804-
ReadOnlyHint: ToBoolPtr(true),
805-
}),
806-
mcp.WithString("owner",
807-
mcp.Required(),
808-
mcp.Description(DescriptionRepositoryOwner),
809-
),
810-
mcp.WithString("repo",
811-
mcp.Required(),
812-
mcp.Description(DescriptionRepositoryName),
813-
),
814-
mcp.WithNumber("job_id",
815-
mcp.Description("The unique identifier of the workflow job (required for single job logs)"),
816-
),
817-
mcp.WithNumber("run_id",
818-
mcp.Description("Workflow run ID (required when using failed_only)"),
819-
),
820-
mcp.WithBoolean("failed_only",
821-
mcp.Description("When true, gets logs for all failed jobs in run_id"),
822-
),
823-
mcp.WithBoolean("return_content",
824-
mcp.Description("Returns actual log content instead of URLs"),
825-
),
826-
mcp.WithNumber("tail_lines",
827-
mcp.Description("Number of lines to return from the end of the log"),
828-
mcp.DefaultNumber(500),
829-
),
830-
),
831-
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
832-
owner, err := RequiredParam[string](request, "owner")
833-
if err != nil {
834-
return mcp.NewToolResultError(err.Error()), nil
835-
}
836-
repo, err := RequiredParam[string](request, "repo")
837-
if err != nil {
838-
return mcp.NewToolResultError(err.Error()), nil
839-
}
840-
841-
// Get optional parameters
842-
jobID, err := OptionalIntParam(request, "job_id")
843-
if err != nil {
844-
return mcp.NewToolResultError(err.Error()), nil
845-
}
846-
runID, err := OptionalIntParam(request, "run_id")
847-
if err != nil {
848-
return mcp.NewToolResultError(err.Error()), nil
849-
}
850-
failedOnly, err := OptionalParam[bool](request, "failed_only")
851-
if err != nil {
852-
return mcp.NewToolResultError(err.Error()), nil
853-
}
854-
returnContent, err := OptionalParam[bool](request, "return_content")
855-
if err != nil {
856-
return mcp.NewToolResultError(err.Error()), nil
857-
}
858-
tailLines, err := OptionalIntParam(request, "tail_lines")
859-
if err != nil {
860-
return mcp.NewToolResultError(err.Error()), nil
861-
}
862-
// Default to 500 lines if not specified
863-
if tailLines == 0 {
864-
tailLines = 500
865-
}
866-
867-
client, err := getClient(ctx)
868-
if err != nil {
869-
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
870-
}
812+
// getWorkflowJobLogs downloads logs for a specific workflow job or efficiently gets all failed job logs for a workflow run
813+
func getWorkflowJobLogs(ctx context.Context, client *github.Client, request mcp.CallToolRequest, owner, repo string, runID int64, jobID int64, returnContent bool, failedOnly bool, tailLines int, contentWindowSize int) (*mcp.CallToolResult, error) {
814+
// Default to 500 lines if not specified
815+
if tailLines == 0 {
816+
tailLines = 500
817+
}
871818

872-
// Validate parameters
873-
if failedOnly && runID == 0 {
874-
return mcp.NewToolResultError("run_id is required when failed_only is true"), nil
875-
}
876-
if !failedOnly && jobID == 0 {
877-
return mcp.NewToolResultError("job_id is required when failed_only is false"), nil
878-
}
819+
// Validate parameters
820+
if failedOnly && runID == 0 {
821+
return mcp.NewToolResultError("resource_id is required when failed_only is true"), nil
822+
}
823+
if !failedOnly && jobID == 0 {
824+
return mcp.NewToolResultError("job_id is required when failed_only is false"), nil
825+
}
879826

880-
if failedOnly && runID > 0 {
881-
// Handle failed-only mode: get logs for all failed jobs in the workflow run
882-
return handleFailedJobLogs(ctx, client, owner, repo, int64(runID), returnContent, tailLines, contentWindowSize)
883-
} else if jobID > 0 {
884-
// Handle single job mode
885-
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines, contentWindowSize)
886-
}
827+
if failedOnly && runID > 0 {
828+
// Handle failed-only mode: get logs for all failed jobs in the workflow run
829+
return handleFailedJobLogs(ctx, client, owner, repo, runID, returnContent, tailLines, contentWindowSize)
830+
} else if jobID > 0 {
831+
// Handle single job mode
832+
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines, contentWindowSize)
833+
}
887834

888-
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
889-
}
835+
return mcp.NewToolResultError("Either job_id must be provided for single job logs, or resource_id with failed_only=true for failed job logs"), nil
890836
}
891837

892838
// handleFailedJobLogs gets logs for all failed jobs in a workflow run

0 commit comments

Comments
 (0)