Skip to content

Commit 824ca2b

Browse files
mpuccioktf
authored andcommitted
Hyperloop MCP: support inspecting train outputs
1 parent 3db945b commit 824ca2b

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

‎Framework/Core/scripts/hyperloop-server/hyperloop_server.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,61 @@ async def train_detail(train_id: int) -> str:
297297
return "\n".join(lines)
298298

299299

300+
@mcp.tool()
301+
async def train_outputs(train_id: int, include_jobs: bool = False) -> dict:
302+
"""Return Grid output directories and merge coverage for one train.
303+
304+
One read-only metadata request. Directory locations are reported by Hyperloop;
305+
this does not assert which files still exist there or download Grid payloads.
306+
Include per-run job directories only when requested. Different merge entries
307+
can refer to the same directory; consumers must deduplicate before downloading.
308+
"""
309+
if train_id <= 0:
310+
raise ValueError("train_id must be positive")
311+
train = await _get("trains/train.jsp", {"train_id": train_id})
312+
if isinstance(train, list):
313+
if len(train) != 1:
314+
raise RuntimeError("Expected a single train record")
315+
train = train[0]
316+
317+
def runs(value):
318+
if not value:
319+
return set()
320+
if isinstance(value, str):
321+
return {int(run.strip()) for run in value.split(",") if run.strip()}
322+
return {int(run) for run in value}
323+
324+
merges = []
325+
for merge in train.get("mergeResults") or []:
326+
intended = runs(merge.get("total_merge"))
327+
merged = runs(merge.get("merged_runlist"))
328+
merges.append({
329+
"id": merge.get("id"),
330+
"name": merge.get("full_name"),
331+
"state": merge.get("merge_state"),
332+
"directory": merge.get("outputdir"),
333+
"merged_run_count": len(merged),
334+
"intended_run_count": len(intended),
335+
"missing_runs": sorted(intended - merged),
336+
"input_data_processed": merge.get("input_data_processed"),
337+
"merge_completion": merge.get("merge_completion"),
338+
})
339+
result = {
340+
"train_id": train.get("id"),
341+
"dataset": train.get("dataset_name"),
342+
"state": train.get("state"),
343+
"package_tag": train.get("package_tag"),
344+
"derived_data_type": train.get("derived_data_type"),
345+
"merges": merges,
346+
}
347+
if include_jobs:
348+
result["jobs"] = [
349+
{key: job.get(key) for key in ("id", "run", "merge_state", "outputdir")}
350+
for job in train.get("jobResults") or []
351+
]
352+
return result
353+
354+
300355
@mcp.tool()
301356
async def wagon_stats(train_id: int) -> str:
302357
"""Get per-wagon CPU and memory breakdown for a train (ongoing or finished).

0 commit comments

Comments
 (0)