package buildkite
import (
"context"
"github.com/buildkite/buildkite-mcp-server/pkg/trace"
"github.com/buildkite/go-buildkite/v4"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"go.opentelemetry.io/otel/attribute"
)
type TestExecutionsClient interface {
GetFailedExecutions(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error)
}
func GetFailedTestExecutions(client TestExecutionsClient) (tool mcp.Tool, handler server.ToolHandlerFunc, scopes []string) {
return mcp.NewTool("get_failed_executions",
mcp.WithDescription("Get failed test executions for a specific test run in Buildkite Test Engine. Optionally get the expanded failure details such as full error messages and stack traces."),
mcp.WithString("org_slug",
mcp.Required(),
),
mcp.WithString("test_suite_slug",
mcp.Required(),
),
mcp.WithString("run_id",
mcp.Required(),
),
mcp.WithBoolean("include_failure_expanded",
mcp.Description("Include the expanded failure details such as full error messages and stack traces. This can be used to explain and diganose the cause of test failures."),
),
withClientSidePagination(),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: "Get Failed Test Executions",
ReadOnlyHint: mcp.ToBoolPtr(true),
}),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
ctx, span := trace.Start(ctx, "buildkite.GetFailedExecutions")
defer span.End()
orgSlug, err := request.RequireString("org_slug")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
testSuiteSlug, err := request.RequireString("test_suite_slug")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
runID, err := request.RequireString("run_id")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
includeFailureExpanded := request.GetBool("include_failure_expanded", false)
// Get client-side pagination parameters (always enabled)
paginationParams := getClientSidePaginationParams(request)
span.SetAttributes(
attribute.String("org_slug", orgSlug),
attribute.String("test_suite_slug", testSuiteSlug),
attribute.String("run_id", runID),
attribute.Bool("include_failure_expanded", includeFailureExpanded),
attribute.Int("page", paginationParams.Page),
attribute.Int("per_page", paginationParams.PerPage),
)
options := &buildkite.FailedExecutionsOptions{
IncludeFailureExpanded: includeFailureExpanded,
}
failedExecutions, _, err := client.GetFailedExecutions(ctx, orgSlug, testSuiteSlug, runID, options)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
// Always apply client-side pagination
result := applyClientSidePagination(failedExecutions, paginationParams)
span.SetAttributes(
attribute.Int("item_count", len(failedExecutions)),
)
return mcpTextResult(span, &result)
}, []string{"read_suites"}
}