Skip to content

Commit 92d8ed2

Browse files
committed
Add running build filters
1 parent 2122ced commit 92d8ed2

3 files changed

Lines changed: 92 additions & 16 deletions

File tree

README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@ Minimal MCP server for discovering Xcode Cloud products and workflows, then retr
1212

1313
## Features
1414

15-
- Discover Xcode Cloud products with `list_products`.
16-
- Discover workflows for a product with `list_workflows`.
17-
- Retrieve workflow configuration with `get_workflow_details`.
18-
- List recent workflow runs with `list_build_runs`.
19-
- Retrieve build issue counts with `get_build_issues`.
20-
- Retrieve and summarize text-like build logs with `get_build_logs`.
21-
- Materialize build logs into a local temp directory with `materialize_build_logs`.
22-
- Save extracted logs to a local temporary directory and return file paths for agent-side inspection.
23-
- Retrieve test summaries with `get_test_results`.
24-
- Retrieve only detected failed tests with `get_failed_tests`.
25-
- Retrieve screenshots, videos, result bundles, and test products with `get_test_artifacts`.
26-
- Clean up saved local log directories with `cleanup_saved_logs`.
15+
| Feature | Tool(s) | Example use | Example return |
16+
| --- | --- | --- | --- |
17+
| Discover products | `list_products` | "Show me the Xcode Cloud products available in this account." | `Demo App`, `productType: APP`, `createdDate: 2026-03-30T10:00:00Z` |
18+
| Discover workflows | `list_workflows` | "List the workflows for product `def456`." | `Feature Branch`, `description`, `isEnabled: true`, `containerFilePath: Chauffeur.xcodeproj` |
19+
| Inspect workflow configuration | `get_workflow_details` | "Show me the full workflow details for `abc123`, including environment and actions." | `general`, `environment`, `startConditions`, `actions`, `postActions` |
20+
| Monitor running or recent builds | `list_build_runs` | "Show me the running builds for workflow `abc123` so I can monitor them." | `number: 93`, `executionProgress: RUNNING`, `completionStatus: null`, `startedDate: ...` |
21+
| See build health quickly | `get_build_issues` | "What went wrong in the latest failing build for workflow `abc123`?" | `issueCounts: { errors: 1, testFailures: 3, warnings: 2 }` |
22+
| Read compact build log summaries | `get_build_logs` | "Retrieve logs of build `81` and summarize the failure." | `failedTests`, `highlights`, `excerpt`, `savedLogsDirectory` |
23+
| Materialize logs for local grep | `materialize_build_logs` | "Download the logs for build `81` so I can grep them locally." | `savedLogsDirectory: /var/folders/...`, `savedLogs: [...]` |
24+
| Summarize test outcomes | `get_test_results` | "Summarize the test results for the latest failing build." | `testFailures`, `issueCounts`, `summary` |
25+
| Jump straight to failed tests | `get_failed_tests` | "What tests failed in build `81`?" | `displayExpiryDateReturnsFormattedDateWhenExpiryDateExists()`, assertion message, saved log paths |
26+
| Retrieve UI test artifacts | `get_test_artifacts` | "Show me the screenshots and videos from the latest failing UI test run." | `screenshots`, `videos`, `resultBundles`, `downloadUrl` |
27+
| Clean up local temp files | `cleanup_saved_logs` | "Remove saved logs older than 24 hours." | `removedDirectories: [...]`, `retainedDirectories: [...]` |
2728

2829
Build lookup is workflow-scoped. Retrieval tools accept a direct `buildRunId`, or a `workflowId` plus `buildNumber`, or a `workflowId` plus `buildSelector: "latest" | "latestFailing"`.
2930

31+
`list_build_runs` also supports `status: "all" | "failed" | "succeeded" | "running" | "pending"`, so agents can poll active workflows without post-processing every run locally.
32+
3033
## Requirements
3134

3235
- Node.js `20+`

src/tools/build-runs.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '../utils/build-locator.js';
1010
import { errorResponse, jsonResponse } from '../utils/tool-response.js';
1111

12-
type BuildRunStatusFilter = 'all' | 'failed' | 'succeeded';
12+
type BuildRunStatusFilter = 'all' | 'failed' | 'pending' | 'running' | 'succeeded';
1313

1414
/**
1515
* Register build run listing tools.
@@ -26,7 +26,7 @@ export function registerBuildRunTools(
2626
inputSchema: {
2727
workflowId: z.string(),
2828
limit: z.number().int().positive().max(200).optional(),
29-
status: z.enum(['all', 'failed', 'succeeded']).optional(),
29+
status: z.enum(['all', 'failed', 'pending', 'running', 'succeeded']).optional(),
3030
},
3131
},
3232
async ({
@@ -84,6 +84,18 @@ function filterBuildRuns(
8484
);
8585
}
8686

87+
if (status === 'pending') {
88+
return buildRuns.filter(
89+
(buildRun) => buildRun.attributes.executionProgress === 'PENDING',
90+
);
91+
}
92+
93+
if (status === 'running') {
94+
return buildRuns.filter(
95+
(buildRun) => buildRun.attributes.executionProgress === 'RUNNING',
96+
);
97+
}
98+
8799
return buildRuns.filter(
88100
(buildRun) => buildRun.attributes.completionStatus === 'SUCCEEDED',
89101
);

tests/tools.test.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,57 @@ test('registered tools expose product, workflow, and build data', async () => {
164164
},
165165
};
166166

167+
const runningBuildRun: CiBuildRun = {
168+
id: 'build-2',
169+
type: 'ciBuildRuns',
170+
attributes: {
171+
number: 43,
172+
createdDate: '2026-03-30T12:30:00Z',
173+
startedDate: '2026-03-30T12:31:00Z',
174+
executionProgress: 'RUNNING',
175+
isPullRequestBuild: true,
176+
issueCounts: {
177+
analyzerWarnings: 0,
178+
errors: 0,
179+
testFailures: 0,
180+
warnings: 0,
181+
},
182+
},
183+
relationships: {
184+
workflow: {
185+
data: {
186+
type: 'ciWorkflows',
187+
id: 'workflow-1',
188+
},
189+
},
190+
},
191+
};
192+
193+
const pendingBuildRun: CiBuildRun = {
194+
id: 'build-3',
195+
type: 'ciBuildRuns',
196+
attributes: {
197+
number: 44,
198+
createdDate: '2026-03-30T12:45:00Z',
199+
executionProgress: 'PENDING',
200+
isPullRequestBuild: true,
201+
issueCounts: {
202+
analyzerWarnings: 0,
203+
errors: 0,
204+
testFailures: 0,
205+
warnings: 0,
206+
},
207+
},
208+
relationships: {
209+
workflow: {
210+
data: {
211+
type: 'ciWorkflows',
212+
id: 'workflow-1',
213+
},
214+
},
215+
},
216+
};
217+
167218
const logArtifact = createArtifact('artifact-1', 'build.log', 'LOG');
168219
const screenshotArtifact = createArtifact(
169220
'artifact-2',
@@ -195,7 +246,7 @@ test('registered tools expose product, workflow, and build data', async () => {
195246
},
196247
builds: {
197248
getById: async () => buildRun,
198-
listForWorkflow: async () => [buildRun],
249+
listForWorkflow: async () => [buildRun, runningBuildRun, pendingBuildRun],
199250
getActions: async () => [buildAction],
200251
},
201252
artifacts: {
@@ -240,6 +291,12 @@ test('registered tools expose product, workflow, and build data', async () => {
240291
const buildsPayload = parsePayload(
241292
await listBuildRuns!({ workflowId: 'workflow-1' }),
242293
);
294+
const runningBuildsPayload = parsePayload(
295+
await listBuildRuns!({ workflowId: 'workflow-1', status: 'running' }),
296+
);
297+
const pendingBuildsPayload = parsePayload(
298+
await listBuildRuns!({ workflowId: 'workflow-1', status: 'pending' }),
299+
);
243300
const workflowDetailsPayload = parsePayload(
244301
await getWorkflowDetails!({ workflowId: 'workflow-1' }),
245302
);
@@ -267,7 +324,11 @@ test('registered tools expose product, workflow, and build data', async () => {
267324
const cleanupPayload = parsePayload(await cleanupSavedLogs!({ maxAgeHours: 1 }));
268325

269326
assert.equal(productsPayload.products[0].id, 'product-1');
270-
assert.equal(buildsPayload.buildRuns[0].number, 42);
327+
assert.equal(buildsPayload.buildRuns[0].number, 44);
328+
assert.equal(runningBuildsPayload.buildRuns.length, 1);
329+
assert.equal(runningBuildsPayload.buildRuns[0].executionProgress, 'RUNNING');
330+
assert.equal(pendingBuildsPayload.buildRuns.length, 1);
331+
assert.equal(pendingBuildsPayload.buildRuns[0].executionProgress, 'PENDING');
271332
assert.equal(workflowDetailsPayload.workflow.general.isEnabled, true);
272333
assert.equal(
273334
workflowDetailsPayload.workflow.environment.repository.repositoryName,

0 commit comments

Comments
 (0)