Skip to content

Commit f4225ea

Browse files
authored
Merge pull request #166 from buildkite/squash_build_jobs
feat: update the get_build to incorporate the get_jobs functionality
2 parents 525e659 + 7ae56df commit f4225ea

File tree

7 files changed

+356
-651
lines changed

7 files changed

+356
-651
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ coverage.out
1111

1212
# debugger binaries
1313
__debug_bin*
14+
/*.json

Makefile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Variables
22
BINARY_NAME := buildkite-mcp-server
3-
CMD_PATH := ./cmd/$(BINARY_NAME)
3+
CMD_PACKAGE := ./cmd/$(BINARY_NAME)
4+
DOCS_PACKAGE := ./cmd/update-docs
45
COVERAGE_FILE := coverage.out
56

67
# Default target
@@ -14,23 +15,23 @@ help: ## Show this help message
1415

1516
.PHONY: build
1617
build: ## Build the binary
17-
go build -o $(BINARY_NAME) $(CMD_PATH)/main.go
18+
go build -o $(BINARY_NAME) $(CMD_PACKAGE)
1819

1920
.PHONY: install
2021
install: ## Install the binary
21-
go install ./cmd/...
22+
go install $(CMD_PACKAGE)
2223

2324
.PHONY: snapshot
2425
snapshot: ## Build snapshot with goreleaser
2526
goreleaser build --snapshot --clean --single-target
2627

2728
.PHONY: update-docs
2829
update-docs: ## Update documentation
29-
go run cmd/update-docs/main.go
30+
go run $(DOCS_PACKAGE)
3031

3132
.PHONY: run
3233
run: ## Run the application with stdio
33-
go run $(CMD_PATH)/main.go stdio
34+
go run $(CMD_PACKAGE) stdio
3435

3536
.PHONY: test
3637
test: ## Run tests with coverage

pkg/buildkite/builds.go

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"strings"
89
"time"
910

1011
"github.com/buildkite/buildkite-mcp-server/pkg/trace"
@@ -76,7 +77,9 @@ type GetBuildArgs struct {
7677
OrgSlug string `json:"org_slug"`
7778
PipelineSlug string `json:"pipeline_slug"`
7879
BuildNumber string `json:"build_number"`
79-
DetailLevel string `json:"detail_level"` // summary, detailed, full
80+
DetailLevel string `json:"detail_level"` // summary, detailed, full
81+
JobState string `json:"job_state"` // NEW: comma-separated states
82+
IncludeAgent bool `json:"include_agent"` // NEW: include full agent details
8083
}
8184

8285
// GetBuildTestEngineRunsArgs struct
@@ -104,29 +107,30 @@ func summarizeBuild(build buildkite.Build) BuildSummary {
104107
}
105108

106109
// detailBuild converts a buildkite.Build to BuildDetail with job summary
107-
func detailBuild(build buildkite.Build) BuildDetail {
110+
// filteredJobs is used for job_summary stats, while build.Jobs is used for jobs_total
111+
func detailBuild(build buildkite.Build, filteredJobs []buildkite.Job) BuildDetail {
108112
summary := summarizeBuild(build)
109113

110-
// Create job summary
114+
// Create job summary from filtered jobs
111115
jobSummary := &JobSummary{
112-
Total: len(build.Jobs),
116+
Total: len(filteredJobs),
113117
ByState: make(map[string]int),
114118
}
115119

116-
for _, job := range build.Jobs {
120+
for _, job := range filteredJobs {
117121
if job.State == "" {
118122
continue
119123
}
120124
jobSummary.ByState[job.State]++
121125
}
122126

123127
return BuildDetail{
124-
BuildSummary: summary,
128+
BuildSummary: summary, // jobs_total reflects ALL jobs (unfiltered)
125129
Source: build.Source,
126130
Author: build.Author,
127131
StartedAt: build.StartedAt,
128132
FinishedAt: build.FinishedAt,
129-
JobSummary: jobSummary,
133+
JobSummary: jobSummary, // job_summary reflects filtered jobs
130134
}
131135
}
132136

@@ -274,7 +278,10 @@ func ListBuilds(client BuildsClient) (tool mcp.Tool, handler mcp.TypedToolHandle
274278
case "summary":
275279
result = createPaginatedBuildResult(builds, summarizeBuild, headers)
276280
case "detailed":
277-
result = createPaginatedBuildResult(builds, detailBuild, headers)
281+
// For list_builds, use all jobs (no filtering)
282+
result = createPaginatedBuildResult(builds, func(b buildkite.Build) BuildDetail {
283+
return detailBuild(b, b.Jobs)
284+
}, headers)
278285
case "full":
279286
result = PaginatedResult[buildkite.Build]{
280287
Items: builds,
@@ -368,6 +375,12 @@ func GetBuild(client BuildsClient) (tool mcp.Tool, handler mcp.TypedToolHandlerF
368375
mcp.WithString("detail_level",
369376
mcp.Description("Response detail level: 'summary' (essential fields), 'detailed' (medium detail), or 'full' (complete build data). Default: 'detailed'"),
370377
),
378+
mcp.WithString("job_state",
379+
mcp.Description("Filter jobs by state. Comma-separated for multiple states (e.g., \"failed,broken,canceled\"). Valid states: scheduled, running, passed, failed, canceled, skipped, broken, waiting, waiting_failed, blocked, etc."),
380+
),
381+
mcp.WithBoolean("include_agent",
382+
mcp.Description("Include full agent details in job objects. When false (default), only agent.id is included to reduce response size."),
383+
),
371384
mcp.WithToolAnnotation(mcp.ToolAnnotation{
372385
Title: "Get Build",
373386
ReadOnlyHint: mcp.ToBoolPtr(true),
@@ -393,6 +406,8 @@ func GetBuild(client BuildsClient) (tool mcp.Tool, handler mcp.TypedToolHandlerF
393406
attribute.String("pipeline_slug", args.PipelineSlug),
394407
attribute.String("build_number", args.BuildNumber),
395408
attribute.String("detail_level", args.DetailLevel),
409+
attribute.String("job_state", args.JobState),
410+
attribute.Bool("include_agent", args.IncludeAgent),
396411
)
397412

398413
// Set default detail level
@@ -418,14 +433,53 @@ func GetBuild(client BuildsClient) (tool mcp.Tool, handler mcp.TypedToolHandlerF
418433
return mcp.NewToolResultError(err.Error()), nil
419434
}
420435

436+
// Parse job states filter
437+
var requestedStates map[string]bool
438+
if args.JobState != "" {
439+
states := strings.Split(args.JobState, ",")
440+
requestedStates = make(map[string]bool, len(states))
441+
for _, state := range states {
442+
requestedStates[strings.TrimSpace(state)] = true
443+
}
444+
}
445+
446+
// Filter jobs if states specified
447+
jobs := build.Jobs
448+
if requestedStates != nil {
449+
filteredJobs := make([]buildkite.Job, 0)
450+
for _, job := range build.Jobs {
451+
if job.State != "" && requestedStates[job.State] {
452+
filteredJobs = append(filteredJobs, job)
453+
}
454+
}
455+
jobs = filteredJobs
456+
}
457+
458+
// Strip agent details if not requested
459+
if !args.IncludeAgent && len(jobs) > 0 {
460+
jobsWithMinimalAgent := make([]buildkite.Job, len(jobs))
461+
for i, job := range jobs {
462+
jobCopy := job
463+
// Keep only agent ID, strip verbose details
464+
jobCopy.Agent = buildkite.Agent{ID: job.Agent.ID}
465+
jobsWithMinimalAgent[i] = jobCopy
466+
}
467+
jobs = jobsWithMinimalAgent
468+
}
469+
421470
var result any
422471
switch detailLevel {
423472
case "summary":
473+
// Summary level ignores job filtering
424474
result = summarizeBuild(build)
425475
case "detailed":
426-
result = detailBuild(build)
476+
// Detailed level uses filtered jobs for job_summary
477+
result = detailBuild(build, jobs)
427478
case "full":
428-
result = build
479+
// Full level returns build with filtered jobs
480+
buildCopy := build
481+
buildCopy.Jobs = jobs
482+
result = buildCopy
429483
default:
430484
return mcp.NewToolResultError("detail_level must be 'summary', 'detailed', or 'full'"), nil
431485
}
@@ -677,8 +731,8 @@ func WaitForBuild(client BuildsClient) (tool mcp.Tool, handler mcp.TypedToolHand
677731
}
678732
}
679733

680-
// default to detailed
681-
result := detailBuild(build)
734+
// default to detailed, use all jobs (no filtering)
735+
result := detailBuild(build, build.Jobs)
682736

683737
return mcpTextResult(span, &result)
684738
}, []string{"read_builds"}

0 commit comments

Comments
 (0)