8e7fa5536e
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1081 lines
40 KiB
Go
1081 lines
40 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/github/github-mcp-server/internal/profiler"
|
|
buffer "github.com/github/github-mcp-server/pkg/buffer"
|
|
ghErrors "github.com/github/github-mcp-server/pkg/errors"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
"github.com/google/go-github/v79/github"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
const (
|
|
DescriptionRepositoryOwner = "Repository owner"
|
|
DescriptionRepositoryName = "Repository name"
|
|
)
|
|
|
|
type actionsActionType int
|
|
|
|
const (
|
|
actionsActionTypeUnknown actionsActionType = iota
|
|
actionsActionTypeListWorkflows
|
|
actionsActionTypeListWorkflowRuns
|
|
actionsActionTypeListWorkflowJobs
|
|
actionsActionTypeListWorkflowArtifacts
|
|
actionsActionTypeGetWorkflow
|
|
actionsActionTypeGetWorkflowRun
|
|
actionsActionTypeGetWorkflowJob
|
|
actionsActionTypeGetWorkflowRunUsage
|
|
actionsActionTypeGetWorkflowRunLogs
|
|
actionsActionTypeGetWorkflowJobLogs
|
|
actionsActionTypeDownloadWorkflowArtifact
|
|
actionsActionTypeRunWorkflow
|
|
actionsActionTypeRerunWorkflowRun
|
|
actionsActionTypeRerunFailedJobs
|
|
actionsActionTypeCancelWorkflowRun
|
|
actionsActionTypeDeleteWorkflowRunLogs
|
|
)
|
|
|
|
var actionsResourceTypes = map[actionsActionType]string{
|
|
actionsActionTypeListWorkflows: "list_workflows",
|
|
actionsActionTypeListWorkflowRuns: "list_workflow_runs",
|
|
actionsActionTypeListWorkflowJobs: "list_workflow_jobs",
|
|
actionsActionTypeListWorkflowArtifacts: "list_workflow_run_artifacts",
|
|
actionsActionTypeGetWorkflow: "get_workflow",
|
|
actionsActionTypeGetWorkflowRun: "get_workflow_run",
|
|
actionsActionTypeGetWorkflowJob: "get_workflow_job",
|
|
actionsActionTypeGetWorkflowRunLogs: "get_workflow_run_logs",
|
|
actionsActionTypeGetWorkflowJobLogs: "get_workflow_job_logs",
|
|
actionsActionTypeGetWorkflowRunUsage: "get_workflow_run_usage",
|
|
actionsActionTypeDownloadWorkflowArtifact: "download_workflow_run_artifact",
|
|
actionsActionTypeRunWorkflow: "run_workflow",
|
|
actionsActionTypeRerunWorkflowRun: "rerun_workflow_run",
|
|
actionsActionTypeRerunFailedJobs: "rerun_failed_jobs",
|
|
actionsActionTypeCancelWorkflowRun: "cancel_workflow_run",
|
|
actionsActionTypeDeleteWorkflowRunLogs: "delete_workflow_run_logs",
|
|
}
|
|
|
|
func (r actionsActionType) String() string {
|
|
if str, ok := actionsResourceTypes[r]; ok {
|
|
return str
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
func actionFromString(s string) actionsActionType {
|
|
for r, str := range actionsResourceTypes {
|
|
if str == strings.ToLower(s) {
|
|
return r
|
|
}
|
|
}
|
|
return actionsActionTypeUnknown
|
|
}
|
|
|
|
func ActionsList(getClient GetClientFn, t translations.TranslationHelperFunc, _ int) (tool mcp.Tool, handler server.ToolHandlerFunc) {
|
|
return mcp.NewTool("actions_list",
|
|
mcp.WithDescription(t("TOOL_ACTIONS_LIST_DESCRIPTION", `Tools for listing GitHub Actions resources.
|
|
Use this tool to list workflows in a repository, or list workflow runs, jobs, and artifacts for a specific workflow or workflow run.
|
|
`)),
|
|
mcp.WithToolAnnotation(mcp.ToolAnnotation{
|
|
Title: t("TOOL_ACTIONS_LIST_USER_TITLE", "List GitHub Actions workflows in a repository"),
|
|
ReadOnlyHint: ToBoolPtr(true),
|
|
}),
|
|
mcp.WithString("action",
|
|
mcp.Required(),
|
|
mcp.Description("The action to perform"),
|
|
mcp.Enum(
|
|
actionsActionTypeListWorkflows.String(),
|
|
actionsActionTypeListWorkflowRuns.String(),
|
|
actionsActionTypeListWorkflowJobs.String(),
|
|
actionsActionTypeListWorkflowArtifacts.String(),
|
|
),
|
|
),
|
|
mcp.WithString("owner",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryOwner),
|
|
),
|
|
mcp.WithString("repo",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryName),
|
|
),
|
|
mcp.WithString("resource_id",
|
|
mcp.Description(`The unique identifier of the resource. This will vary based on the "action" provided, so ensure you provide the correct ID:
|
|
- Do not provide any resource ID for 'list_workflows' action.
|
|
- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' actions.
|
|
- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' actions.
|
|
`),
|
|
),
|
|
mcp.WithObject("workflow_runs_filter",
|
|
mcp.Description("Filters for workflow runs. **ONLY** used when action is 'list_workflow_runs'"),
|
|
mcp.Properties(map[string]any{
|
|
"actor": map[string]any{
|
|
"type": "string",
|
|
"description": "Filter to a specific GitHub user's workflow runs.",
|
|
},
|
|
"branch": map[string]any{
|
|
"type": "string",
|
|
"description": "Filter workflow runs to a specific Git branch. Use the name of the branch.",
|
|
},
|
|
"event": map[string]any{
|
|
"type": "string",
|
|
"description": "Filter workflow runs to a specific event type",
|
|
"enum": []string{
|
|
"branch_protection_rule",
|
|
"check_run",
|
|
"check_suite",
|
|
"create",
|
|
"delete",
|
|
"deployment",
|
|
"deployment_status",
|
|
"discussion",
|
|
"discussion_comment",
|
|
"fork",
|
|
"gollum",
|
|
"issue_comment",
|
|
"issues",
|
|
"label",
|
|
"merge_group",
|
|
"milestone",
|
|
"page_build",
|
|
"public",
|
|
"pull_request",
|
|
"pull_request_review",
|
|
"pull_request_review_comment",
|
|
"pull_request_target",
|
|
"push",
|
|
"registry_package",
|
|
"release",
|
|
"repository_dispatch",
|
|
"schedule",
|
|
"status",
|
|
"watch",
|
|
"workflow_call",
|
|
"workflow_dispatch",
|
|
"workflow_run",
|
|
},
|
|
},
|
|
"status": map[string]any{
|
|
"type": "string",
|
|
"description": "Filter workflow runs to only runs with a specific status",
|
|
"enum": []string{"queued", "in_progress", "completed", "requested", "waiting"},
|
|
},
|
|
}),
|
|
),
|
|
mcp.WithObject("workflow_jobs_filter",
|
|
mcp.Description("Filters for workflow jobs. **ONLY** used when action is 'list_workflow_jobs'"),
|
|
mcp.Properties(map[string]any{
|
|
"filter": map[string]any{
|
|
"type": "string",
|
|
"description": "Filters jobs by their completed_at timestamp",
|
|
"enum": []string{"latest", "all"},
|
|
},
|
|
}),
|
|
),
|
|
WithPagination(),
|
|
),
|
|
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := RequiredParam[string](request, "owner")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
repo, err := RequiredParam[string](request, "repo")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
actionTypeStr, err := RequiredParam[string](request, "action")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
resourceType := actionFromString(actionTypeStr)
|
|
if resourceType == actionsActionTypeUnknown {
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
}
|
|
|
|
resourceID, err := OptionalParam[string](request, "resource_id")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
pagination, err := OptionalPaginationParams(request)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
client, err := getClient(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
|
|
}
|
|
|
|
var resourceIDInt int64
|
|
var parseErr error
|
|
switch resourceType {
|
|
case actionsActionTypeListWorkflows:
|
|
// Do nothing, no resource ID needed
|
|
default:
|
|
if resourceID == "" {
|
|
return mcp.NewToolResultError(fmt.Sprintf("missing required parameter for action %s: resource_id", actionTypeStr)), nil
|
|
}
|
|
|
|
// For other actions, resource ID must be an integer
|
|
resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64)
|
|
if parseErr != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for action %s: %v", actionTypeStr, parseErr)), nil
|
|
}
|
|
}
|
|
|
|
switch resourceType {
|
|
case actionsActionTypeListWorkflows:
|
|
return listWorkflows(ctx, client, request, owner, repo, pagination)
|
|
case actionsActionTypeListWorkflowRuns:
|
|
return listWorkflowRuns(ctx, client, request, owner, repo, resourceID, pagination)
|
|
case actionsActionTypeListWorkflowJobs:
|
|
return listWorkflowJobs(ctx, client, request, owner, repo, resourceIDInt, pagination)
|
|
case actionsActionTypeListWorkflowArtifacts:
|
|
return listWorkflowArtifacts(ctx, client, request, owner, repo, resourceIDInt, pagination)
|
|
case actionsActionTypeUnknown:
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
default:
|
|
// Should not reach here
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func ActionsGet(getClient GetClientFn, t translations.TranslationHelperFunc, contentWindowSize int) (tool mcp.Tool, handler server.ToolHandlerFunc) {
|
|
return mcp.NewTool("actions_get",
|
|
mcp.WithDescription(t("TOOL_ACTIONS_READ_DESCRIPTION", `Tools for reading specific GitHub Actions resources.
|
|
Use this tool to get details about individual workflows, workflow runs, jobs, and artifacts, by using their unique IDs.
|
|
`)),
|
|
mcp.WithToolAnnotation(mcp.ToolAnnotation{
|
|
Title: t("TOOL_ACTIONS_READ_USER_TITLE", "Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts)"),
|
|
ReadOnlyHint: ToBoolPtr(true),
|
|
}),
|
|
mcp.WithString("action",
|
|
mcp.Required(),
|
|
mcp.Description("The action to perform"),
|
|
mcp.Enum(
|
|
actionsActionTypeGetWorkflow.String(),
|
|
actionsActionTypeGetWorkflowRun.String(),
|
|
actionsActionTypeGetWorkflowJob.String(),
|
|
actionsActionTypeDownloadWorkflowArtifact.String(),
|
|
actionsActionTypeGetWorkflowRunUsage.String(),
|
|
actionsActionTypeGetWorkflowRunLogs.String(),
|
|
actionsActionTypeGetWorkflowJobLogs.String(),
|
|
),
|
|
),
|
|
mcp.WithString("owner",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryOwner),
|
|
),
|
|
mcp.WithString("repo",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryName),
|
|
),
|
|
mcp.WithString("resource_id",
|
|
mcp.Description(`The unique identifier of the resource. This will vary based on the "action" provided, so ensure you provide the correct ID:
|
|
- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' action.
|
|
- Provide a workflow run ID for 'get_workflow_run', 'download_workflow_run_artifact', 'get_workflow_run_usage', and 'get_workflow_run_logs' actions.
|
|
- Provide a job ID for the 'get_workflow_job' action.
|
|
- Provide a workflow run ID for 'get_workflow_job_logs' action when using failed_only parameter.
|
|
`),
|
|
),
|
|
mcp.WithNumber("job_id",
|
|
mcp.Description("The unique identifier of the workflow job (required for single job logs when action is 'get_workflow_job_logs')"),
|
|
),
|
|
mcp.WithBoolean("failed_only",
|
|
mcp.Description("When true, gets logs for all failed jobs in the workflow run specified by resource_id (only for 'get_workflow_job_logs' action)"),
|
|
),
|
|
mcp.WithBoolean("return_content",
|
|
mcp.Description("Returns actual log content instead of URLs (only for 'get_workflow_job_logs' action)"),
|
|
),
|
|
mcp.WithNumber("tail_lines",
|
|
mcp.Description("Number of lines to return from the end of the log (only for 'get_workflow_job_logs' action)"),
|
|
mcp.DefaultNumber(500),
|
|
),
|
|
),
|
|
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := RequiredParam[string](request, "owner")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
repo, err := RequiredParam[string](request, "repo")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
actionTypeStr, err := RequiredParam[string](request, "action")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
resourceType := actionFromString(actionTypeStr)
|
|
if resourceType == actionsActionTypeUnknown {
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
}
|
|
|
|
var resourceID string
|
|
if resourceType == actionsActionTypeGetWorkflowJobLogs {
|
|
resourceID, err = OptionalParam[string](request, "resource_id")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
} else {
|
|
resourceID, err = RequiredParam[string](request, "resource_id")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
}
|
|
|
|
// Get optional parameters for get_workflow_job_logs
|
|
jobID, err := OptionalIntParam(request, "job_id")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
failedOnly, err := OptionalParam[bool](request, "failed_only")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
returnContent, err := OptionalParam[bool](request, "return_content")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
tailLines, err := OptionalIntParam(request, "tail_lines")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
client, err := getClient(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
|
|
}
|
|
|
|
var resourceIDInt int64
|
|
var parseErr error
|
|
switch resourceType {
|
|
case actionsActionTypeGetWorkflow:
|
|
// Do nothing, we accept both a string workflow ID or filename
|
|
default:
|
|
if resourceID == "" {
|
|
if resourceType != actionsActionTypeGetWorkflowJobLogs {
|
|
return mcp.NewToolResultError(fmt.Sprintf("missing required parameter for action %s: resource_id", actionTypeStr)), nil
|
|
}
|
|
} else {
|
|
// For other actions, resource ID must be an integer
|
|
resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64)
|
|
if parseErr != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for action %s: %v", actionTypeStr, parseErr)), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
switch resourceType {
|
|
case actionsActionTypeGetWorkflow:
|
|
return getWorkflow(ctx, client, request, owner, repo, resourceID)
|
|
case actionsActionTypeGetWorkflowRun:
|
|
return getWorkflowRun(ctx, client, request, owner, repo, resourceIDInt)
|
|
case actionsActionTypeGetWorkflowJob:
|
|
return getWorkflowJob(ctx, client, request, owner, repo, resourceIDInt)
|
|
case actionsActionTypeDownloadWorkflowArtifact:
|
|
return downloadWorkflowArtifact(ctx, client, request, owner, repo, resourceIDInt)
|
|
case actionsActionTypeGetWorkflowRunUsage:
|
|
return getWorkflowRunUsage(ctx, client, request, owner, repo, resourceIDInt)
|
|
case actionsActionTypeGetWorkflowRunLogs:
|
|
return getWorkflowRunLogs(ctx, client, request, owner, repo, resourceIDInt)
|
|
case actionsActionTypeGetWorkflowJobLogs:
|
|
return getWorkflowJobLogs(ctx, client, request, owner, repo, resourceIDInt, int64(jobID), returnContent, failedOnly, tailLines, contentWindowSize)
|
|
case actionsActionTypeUnknown:
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
default:
|
|
// Should not reach here
|
|
return mcp.NewToolResultError("unhandled action type"), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func ActionsRunTrigger(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
|
|
return mcp.NewTool("actions_run_trigger",
|
|
mcp.WithDescription(t("TOOL_ACTIONS_TRIGGER_DESCRIPTION", "Trigger GitHub Actions workflow actions, including running, re-running, cancelling, and deleting workflow runs.")),
|
|
mcp.WithToolAnnotation(mcp.ToolAnnotation{
|
|
Title: t("TOOL_ACTIONS_TRIGGER_USER_TITLE", "Trigger GitHub Actions workflow actions"),
|
|
ReadOnlyHint: ToBoolPtr(false),
|
|
DestructiveHint: ToBoolPtr(true),
|
|
}),
|
|
mcp.WithString("action",
|
|
mcp.Required(),
|
|
mcp.Description("The action to trigger"),
|
|
mcp.Enum(
|
|
actionsActionTypeRunWorkflow.String(),
|
|
actionsActionTypeRerunWorkflowRun.String(),
|
|
actionsActionTypeRerunFailedJobs.String(),
|
|
actionsActionTypeCancelWorkflowRun.String(),
|
|
actionsActionTypeDeleteWorkflowRunLogs.String(),
|
|
),
|
|
),
|
|
mcp.WithString("owner",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryOwner),
|
|
),
|
|
mcp.WithString("repo",
|
|
mcp.Required(),
|
|
mcp.Description(DescriptionRepositoryName),
|
|
),
|
|
mcp.WithString("workflow_id",
|
|
mcp.Description("The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' action."),
|
|
),
|
|
mcp.WithString("ref",
|
|
mcp.Description("The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' action."),
|
|
),
|
|
mcp.WithObject("inputs",
|
|
mcp.Description("Inputs the workflow accepts. Only used for 'run_workflow' action."),
|
|
),
|
|
mcp.WithNumber("run_id",
|
|
mcp.Description("The ID of the workflow run. Required for all actions except 'run_workflow'."),
|
|
),
|
|
),
|
|
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := RequiredParam[string](request, "owner")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
repo, err := RequiredParam[string](request, "repo")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
actionTypeStr, err := RequiredParam[string](request, "action")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
resourceType := actionFromString(actionTypeStr)
|
|
if resourceType == actionsActionTypeUnknown {
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
}
|
|
|
|
// Get optional parameters
|
|
workflowID, _ := OptionalParam[string](request, "workflow_id")
|
|
ref, _ := OptionalParam[string](request, "ref")
|
|
runID, _ := OptionalIntParam(request, "run_id")
|
|
|
|
// Get optional inputs parameter
|
|
var inputs map[string]interface{}
|
|
if requestInputs, ok := request.GetArguments()["inputs"]; ok {
|
|
if inputsMap, ok := requestInputs.(map[string]interface{}); ok {
|
|
inputs = inputsMap
|
|
}
|
|
}
|
|
|
|
// Validate required parameters based on action type
|
|
if resourceType == actionsActionTypeRunWorkflow {
|
|
if workflowID == "" {
|
|
return mcp.NewToolResultError("workflow_id is required for run_workflow action"), nil
|
|
}
|
|
if ref == "" {
|
|
return mcp.NewToolResultError("ref is required for run_workflow action"), nil
|
|
}
|
|
} else if runID == 0 {
|
|
return mcp.NewToolResultError("missing required parameter: run_id"), nil
|
|
}
|
|
|
|
client, err := getClient(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
|
|
}
|
|
|
|
switch resourceType {
|
|
case actionsActionTypeRunWorkflow:
|
|
return runWorkflow(ctx, client, request, owner, repo, workflowID, ref, inputs)
|
|
case actionsActionTypeRerunWorkflowRun:
|
|
return rerunWorkflowRun(ctx, client, request, owner, repo, int64(runID))
|
|
case actionsActionTypeRerunFailedJobs:
|
|
return rerunFailedJobs(ctx, client, request, owner, repo, int64(runID))
|
|
case actionsActionTypeCancelWorkflowRun:
|
|
return cancelWorkflowRun(ctx, client, request, owner, repo, int64(runID))
|
|
case actionsActionTypeDeleteWorkflowRunLogs:
|
|
return deleteWorkflowRunLogs(ctx, client, request, owner, repo, int64(runID))
|
|
case actionsActionTypeUnknown:
|
|
return mcp.NewToolResultError(fmt.Sprintf("unknown action: %s", actionTypeStr)), nil
|
|
default:
|
|
// Should not reach here
|
|
return mcp.NewToolResultError("unhandled action type"), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func getWorkflow(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID string) (*mcp.CallToolResult, error) {
|
|
var workflow *github.Workflow
|
|
var resp *github.Response
|
|
var err error
|
|
|
|
if workflowIDInt, parseErr := strconv.ParseInt(resourceID, 10, 64); parseErr == nil {
|
|
workflow, resp, err = client.Actions.GetWorkflowByID(ctx, owner, repo, workflowIDInt)
|
|
} else {
|
|
workflow, resp, err = client.Actions.GetWorkflowByFileName(ctx, owner, repo, resourceID)
|
|
}
|
|
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow", resp, err), nil
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
r, err := json.Marshal(workflow)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal workflow: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func getWorkflowRun(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID int64) (*mcp.CallToolResult, error) {
|
|
workflowRun, resp, err := client.Actions.GetWorkflowRunByID(ctx, owner, repo, resourceID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow run", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
r, err := json.Marshal(workflowRun)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal workflow run: %w", err)
|
|
}
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func listWorkflowRuns(ctx context.Context, client *github.Client, request mcp.CallToolRequest, owner, repo string, resourceID string, pagination PaginationParams) (*mcp.CallToolResult, error) {
|
|
filterArgs, err := OptionalParam[map[string]any](request, "workflow_runs_filter")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
filterArgsTyped := make(map[string]string)
|
|
for k, v := range filterArgs {
|
|
if strVal, ok := v.(string); ok {
|
|
filterArgsTyped[k] = strVal
|
|
} else {
|
|
filterArgsTyped[k] = ""
|
|
}
|
|
}
|
|
|
|
listWorkflowRunsOptions := &github.ListWorkflowRunsOptions{
|
|
Actor: filterArgsTyped["actor"],
|
|
Branch: filterArgsTyped["branch"],
|
|
Event: filterArgsTyped["event"],
|
|
Status: filterArgsTyped["status"],
|
|
ListOptions: github.ListOptions{
|
|
Page: pagination.Page,
|
|
PerPage: pagination.PerPage,
|
|
},
|
|
}
|
|
|
|
var workflowRuns *github.WorkflowRuns
|
|
var resp *github.Response
|
|
|
|
if workflowIDInt, parseErr := strconv.ParseInt(resourceID, 10, 64); parseErr == nil {
|
|
workflowRuns, resp, err = client.Actions.ListWorkflowRunsByID(ctx, owner, repo, workflowIDInt, listWorkflowRunsOptions)
|
|
} else {
|
|
workflowRuns, resp, err = client.Actions.ListWorkflowRunsByFileName(ctx, owner, repo, resourceID, listWorkflowRunsOptions)
|
|
}
|
|
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list workflow runs", resp, err), nil
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
r, err := json.Marshal(workflowRuns)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal workflow runs: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func getWorkflowJob(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID int64) (*mcp.CallToolResult, error) {
|
|
workflowJob, resp, err := client.Actions.GetWorkflowJobByID(ctx, owner, repo, resourceID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow job", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
r, err := json.Marshal(workflowJob)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal workflow job: %w", err)
|
|
}
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func listWorkflowJobs(ctx context.Context, client *github.Client, request mcp.CallToolRequest, owner, repo string, resourceID int64, pagination PaginationParams) (*mcp.CallToolResult, error) {
|
|
filterArgs, err := OptionalParam[map[string]any](request, "workflow_jobs_filter")
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
|
|
filterArgsTyped := make(map[string]string)
|
|
for k, v := range filterArgs {
|
|
if strVal, ok := v.(string); ok {
|
|
filterArgsTyped[k] = strVal
|
|
} else {
|
|
filterArgsTyped[k] = ""
|
|
}
|
|
}
|
|
|
|
workflowJobs, resp, err := client.Actions.ListWorkflowJobs(ctx, owner, repo, resourceID, &github.ListWorkflowJobsOptions{
|
|
Filter: filterArgsTyped["filter"],
|
|
ListOptions: github.ListOptions{
|
|
Page: pagination.Page,
|
|
PerPage: pagination.PerPage,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list workflow jobs", resp, err), nil
|
|
}
|
|
|
|
// Add optimization tip for failed job debugging
|
|
response := map[string]any{
|
|
"jobs": workflowJobs,
|
|
"optimization_tip": "For debugging failed jobs, consider using get_workflow_job_logs with failed_only=true and run_id=" + fmt.Sprintf("%d", resourceID) + " to get logs directly without needing to list jobs first",
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
r, err := json.Marshal(response)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal workflow jobs: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func downloadWorkflowArtifact(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID int64) (*mcp.CallToolResult, error) {
|
|
// Get the download URL for the artifact
|
|
url, resp, err := client.Actions.DownloadArtifact(ctx, owner, repo, resourceID, 1)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get artifact download URL", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Create response with the download URL and information
|
|
result := map[string]any{
|
|
"download_url": url.String(),
|
|
"message": "Artifact is available for download",
|
|
"note": "The download_url provides a download link for the artifact as a ZIP archive. The link is temporary and expires after a short time.",
|
|
"artifact_id": resourceID,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
func listWorkflowArtifacts(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID int64, pagination PaginationParams) (*mcp.CallToolResult, error) {
|
|
// Set up list options
|
|
opts := &github.ListOptions{
|
|
PerPage: pagination.PerPage,
|
|
Page: pagination.Page,
|
|
}
|
|
|
|
artifacts, resp, err := client.Actions.ListWorkflowRunArtifacts(ctx, owner, repo, resourceID, opts)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list workflow run artifacts", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
r, err := json.Marshal(artifacts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// listWorkflows lists workflows in a repository.
|
|
func listWorkflows(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, pagination PaginationParams) (*mcp.CallToolResult, error) {
|
|
// Set up list options
|
|
opts := &github.ListOptions{
|
|
PerPage: pagination.PerPage,
|
|
Page: pagination.Page,
|
|
}
|
|
|
|
workflows, resp, err := client.Actions.ListWorkflows(ctx, owner, repo, opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list workflows: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
r, err := json.Marshal(workflows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// runWorkflow runs an Actions workflow by workflow ID or filename
|
|
func runWorkflow(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo, workflowID, ref string, inputs map[string]interface{}) (*mcp.CallToolResult, error) {
|
|
event := github.CreateWorkflowDispatchEventRequest{
|
|
Ref: ref,
|
|
Inputs: inputs,
|
|
}
|
|
|
|
var resp *github.Response
|
|
var err error
|
|
var workflowType string
|
|
|
|
if workflowIDInt, parseErr := strconv.ParseInt(workflowID, 10, 64); parseErr == nil {
|
|
resp, err = client.Actions.CreateWorkflowDispatchEventByID(ctx, owner, repo, workflowIDInt, event)
|
|
workflowType = "workflow_id"
|
|
} else {
|
|
resp, err = client.Actions.CreateWorkflowDispatchEventByFileName(ctx, owner, repo, workflowID, event)
|
|
workflowType = "workflow_file"
|
|
}
|
|
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to run workflow", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"message": "Workflow run has been queued",
|
|
"workflow_type": workflowType,
|
|
"workflow_id": workflowID,
|
|
"ref": ref,
|
|
"inputs": inputs,
|
|
"status": resp.Status,
|
|
"status_code": resp.StatusCode,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// getWorkflowRunLogs is a helper function that retrieves the download URL for logs of a specific workflow run.
|
|
func getWorkflowRunLogs(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
|
|
// Get the download URL for the logs
|
|
url, resp, err := client.Actions.GetWorkflowRunLogs(ctx, owner, repo, runID, 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get workflow run logs: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Create response with the logs URL and information
|
|
result := map[string]any{
|
|
"logs_url": url.String(),
|
|
"message": "Workflow run logs are available for download",
|
|
"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.",
|
|
"warning": "This downloads ALL logs as a ZIP file which can be large and expensive. For debugging failed jobs, consider using get_workflow_job_logs with failed_only=true and run_id instead.",
|
|
"optimization_tip": "Use: get_workflow_job_logs with parameters {run_id: " + fmt.Sprintf("%d", runID) + ", failed_only: true} for more efficient failed job debugging",
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// getWorkflowJobLogs downloads logs for a specific workflow job or efficiently gets all failed job logs for a workflow run
|
|
func getWorkflowJobLogs(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64, jobID int64, returnContent bool, failedOnly bool, tailLines int, contentWindowSize int) (*mcp.CallToolResult, error) {
|
|
// Default to 500 lines if not specified
|
|
if tailLines == 0 {
|
|
tailLines = 500
|
|
}
|
|
|
|
// Validate parameters
|
|
if failedOnly && runID == 0 {
|
|
return mcp.NewToolResultError("resource_id is required when failed_only is true"), nil
|
|
}
|
|
if !failedOnly && jobID == 0 {
|
|
return mcp.NewToolResultError("job_id is required when failed_only is false"), nil
|
|
}
|
|
|
|
if failedOnly && runID > 0 {
|
|
// Handle failed-only mode: get logs for all failed jobs in the workflow run
|
|
return handleFailedJobLogs(ctx, client, owner, repo, runID, returnContent, tailLines, contentWindowSize)
|
|
} else if jobID > 0 {
|
|
// Handle single job mode
|
|
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines, contentWindowSize)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// handleFailedJobLogs gets logs for all failed jobs in a workflow run
|
|
func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo string, runID int64, returnContent bool, tailLines int, contentWindowSize int) (*mcp.CallToolResult, error) {
|
|
// First, get all jobs for the workflow run
|
|
jobs, resp, err := client.Actions.ListWorkflowJobs(ctx, owner, repo, runID, &github.ListWorkflowJobsOptions{
|
|
Filter: "latest",
|
|
})
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list workflow jobs", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Filter for failed jobs
|
|
var failedJobs []*github.WorkflowJob
|
|
for _, job := range jobs.Jobs {
|
|
if job.GetConclusion() == "failure" {
|
|
failedJobs = append(failedJobs, job)
|
|
}
|
|
}
|
|
|
|
if len(failedJobs) == 0 {
|
|
result := map[string]any{
|
|
"message": "No failed jobs found in this workflow run",
|
|
"run_id": runID,
|
|
"total_jobs": len(jobs.Jobs),
|
|
"failed_jobs": 0,
|
|
}
|
|
r, _ := json.Marshal(result)
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// Collect logs for all failed jobs
|
|
var logResults []map[string]any
|
|
for _, job := range failedJobs {
|
|
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, job.GetID(), job.GetName(), returnContent, tailLines, contentWindowSize)
|
|
if err != nil {
|
|
// Continue with other jobs even if one fails
|
|
jobResult = map[string]any{
|
|
"job_id": job.GetID(),
|
|
"job_name": job.GetName(),
|
|
"error": err.Error(),
|
|
}
|
|
// Enable reporting of status codes and error causes
|
|
_, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get job logs", resp, err) // Explicitly ignore error for graceful handling
|
|
}
|
|
|
|
logResults = append(logResults, jobResult)
|
|
}
|
|
|
|
result := map[string]any{
|
|
"message": fmt.Sprintf("Retrieved logs for %d failed jobs", len(failedJobs)),
|
|
"run_id": runID,
|
|
"total_jobs": len(jobs.Jobs),
|
|
"failed_jobs": len(failedJobs),
|
|
"logs": logResults,
|
|
"return_format": map[string]bool{"content": returnContent, "urls": !returnContent},
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// handleSingleJobLogs gets logs for a single job
|
|
func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo string, jobID int64, returnContent bool, tailLines int, contentWindowSize int) (*mcp.CallToolResult, error) {
|
|
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, jobID, "", returnContent, tailLines, contentWindowSize)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get job logs", resp, err), nil
|
|
}
|
|
|
|
r, err := json.Marshal(jobResult)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// getJobLogData retrieves log data for a single job, either as URL or content
|
|
func getJobLogData(ctx context.Context, client *github.Client, owner, repo string, jobID int64, jobName string, returnContent bool, tailLines int, contentWindowSize int) (map[string]any, *github.Response, error) {
|
|
// Get the download URL for the job logs
|
|
url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1)
|
|
if err != nil {
|
|
return nil, resp, fmt.Errorf("failed to get job logs for job %d: %w", jobID, err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"job_id": jobID,
|
|
}
|
|
if jobName != "" {
|
|
result["job_name"] = jobName
|
|
}
|
|
|
|
if returnContent {
|
|
// Download and return the actual log content
|
|
content, originalLength, httpResp, err := downloadLogContent(ctx, url.String(), tailLines, contentWindowSize) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
|
|
if err != nil {
|
|
// To keep the return value consistent wrap the response as a GitHub Response
|
|
ghRes := &github.Response{
|
|
Response: httpResp,
|
|
}
|
|
return nil, ghRes, fmt.Errorf("failed to download log content for job %d: %w", jobID, err)
|
|
}
|
|
result["logs_content"] = content
|
|
result["message"] = "Job logs content retrieved successfully"
|
|
result["original_length"] = originalLength
|
|
} else {
|
|
// Return just the URL
|
|
result["logs_url"] = url.String()
|
|
result["message"] = "Job logs are available for download"
|
|
result["note"] = "The logs_url provides a download link for the individual job logs in plain text format. Use return_content=true to get the actual log content."
|
|
}
|
|
|
|
return result, resp, nil
|
|
}
|
|
|
|
func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLines int) (string, int, *http.Response, error) {
|
|
prof := profiler.New(nil, profiler.IsProfilingEnabled())
|
|
finish := prof.Start(ctx, "log_buffer_processing")
|
|
|
|
httpResp, err := http.Get(logURL) //nolint:gosec
|
|
if err != nil {
|
|
return "", 0, httpResp, fmt.Errorf("failed to download logs: %w", err)
|
|
}
|
|
defer func() { _ = httpResp.Body.Close() }()
|
|
|
|
if httpResp.StatusCode != http.StatusOK {
|
|
return "", 0, httpResp, fmt.Errorf("failed to download logs: HTTP %d", httpResp.StatusCode)
|
|
}
|
|
|
|
bufferSize := tailLines
|
|
if bufferSize > maxLines {
|
|
bufferSize = maxLines
|
|
}
|
|
|
|
processedInput, totalLines, httpResp, err := buffer.ProcessResponseAsRingBufferToEnd(httpResp, bufferSize)
|
|
if err != nil {
|
|
return "", 0, httpResp, fmt.Errorf("failed to process log content: %w", err)
|
|
}
|
|
|
|
lines := strings.Split(processedInput, "\n")
|
|
if len(lines) > tailLines {
|
|
lines = lines[len(lines)-tailLines:]
|
|
}
|
|
finalResult := strings.Join(lines, "\n")
|
|
|
|
_ = finish(len(lines), int64(len(finalResult)))
|
|
|
|
return finalResult, totalLines, httpResp, nil
|
|
}
|
|
|
|
// RerunWorkflowRun creates a tool to re-run an entire workflow run
|
|
func rerunWorkflowRun(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
|
|
resp, err := client.Actions.RerunWorkflowByID(ctx, owner, repo, runID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to rerun workflow run", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"message": "Workflow run has been queued for re-run",
|
|
"run_id": runID,
|
|
"status": resp.Status,
|
|
"status_code": resp.StatusCode,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// RerunFailedJobs creates a tool to re-run only the failed jobs in a workflow run
|
|
func rerunFailedJobs(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
|
|
resp, err := client.Actions.RerunFailedJobsByID(ctx, owner, repo, runID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to rerun failed jobs", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"message": "Failed jobs have been queued for re-run",
|
|
"run_id": runID,
|
|
"status": resp.Status,
|
|
"status_code": resp.StatusCode,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// CancelWorkflowRun creates a tool to cancel a workflow run
|
|
func cancelWorkflowRun(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
|
|
resp, err := client.Actions.CancelWorkflowRunByID(ctx, owner, repo, runID)
|
|
if err != nil {
|
|
if _, ok := err.(*github.AcceptedError); !ok {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to cancel workflow run", resp, err), nil
|
|
}
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"message": "Workflow run has been cancelled",
|
|
"run_id": runID,
|
|
"status": resp.Status,
|
|
"status_code": resp.StatusCode,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// deleteWorkflowRunLogs deletes logs for a workflow run
|
|
func deleteWorkflowRunLogs(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, runID int64) (*mcp.CallToolResult, error) {
|
|
resp, err := client.Actions.DeleteWorkflowRunLogs(ctx, owner, repo, runID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to delete workflow run logs", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
result := map[string]any{
|
|
"message": "Workflow run logs have been deleted",
|
|
"run_id": runID,
|
|
"status": resp.Status,
|
|
"status_code": resp.StatusCode,
|
|
}
|
|
|
|
r, err := json.Marshal(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|
|
|
|
// GetWorkflowRunUsage creates a tool to get usage metrics for a workflow run
|
|
func getWorkflowRunUsage(ctx context.Context, client *github.Client, _ mcp.CallToolRequest, owner, repo string, resourceID int64) (*mcp.CallToolResult, error) {
|
|
usage, resp, err := client.Actions.GetWorkflowRunUsageByID(ctx, owner, repo, resourceID)
|
|
if err != nil {
|
|
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow run usage", resp, err), nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
r, err := json.Marshal(usage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
|
}
|
|
|
|
return mcp.NewToolResultText(string(r)), nil
|
|
}
|