Skip to content

Commit f1fd11f

Browse files
committed
fetch tracks per assembly
1 parent 300ff2d commit f1fd11f

2 files changed

Lines changed: 97 additions & 5 deletions

File tree

src/server/mcp_server.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def list_species() -> list:
1919
genomes = tools.fetch_ucsc_genomes()
2020
return tools.get_species(genomes)
2121

22-
2322
@mcp.tool(
2423
name="list_assemblies",
2524
description="Get assemblies for a given species (exact or fuzzy match)",
@@ -47,17 +46,52 @@ def list_assemblies(species_name: str) -> list:
4746
genomes = tools.fetch_ucsc_genomes()
4847
return tools.get_assemblies(species_name, genomes)
4948

49+
@mcp.tool(
50+
name="list_ucsc_tracks",
51+
description="List all UCSC genome browser tracks for a given assembly/genome",
52+
output_schema={
53+
"type": "object",
54+
"properties": {
55+
"genome": {"type": "string"},
56+
"tracks": {"type": "object"},
57+
"error": {"type": "string"},
58+
},
59+
"required": ["genome", "tracks"]
60+
}
61+
)
62+
def list_ucsc_tracks_tool(genome: str, timeout: int = 10) -> dict:
63+
"""
64+
MCP tool to fetch all UCSC tracks for a given genome/assembly.
65+
66+
Args:
67+
genome (str): Assembly name (e.g., hg38, mm10)
68+
timeout (int): Request timeout in seconds
69+
70+
Returns:
71+
dict: {
72+
"genome": genome,
73+
"tracks": { ... full UCSC tracks JSON ... },
74+
"error": "..." # optional, only if something fails
75+
}
76+
"""
77+
tracks = tools.list_ucsc_tracks(genome, timeout=timeout)
78+
return {
79+
"genome": genome,
80+
"tracks": tracks if "error" not in tracks else {},
81+
"error": tracks.get("error")
82+
}
83+
5084
@mcp.tool(
5185
name="lift_over_coordinates",
5286
description="Convert genomic coordinates between assemblies using UCSC liftOver.",
53-
output_schema={
87+
output_schema={
5488
"type": "object",
5589
"properties": {
5690
"input": {"type": "string"},
5791
"from": {"type": "string"},
5892
"to": {"type": "string"},
59-
"output": {"type": "string"},
60-
"error": {"type": "string"},
93+
"output": {"type": ["string", "null"]},
94+
"error": {"type": ["string", "null"]},
6195
},
6296
"required": ["from", "to"],
6397
},
@@ -127,7 +161,6 @@ def list_species_api():
127161
genomes = tools.fetch_ucsc_genomes()
128162
return tools.get_species(genomes)
129163

130-
131164
@app.get("/assemblies/{species_name}")
132165
def list_assemblies_api(species_name: str, exact: bool = Query(True, description="Set to false for partial name matches")):
133166
"""
@@ -138,6 +171,17 @@ def list_assemblies_api(species_name: str, exact: bool = Query(True, description
138171
genomes = tools.fetch_ucsc_genomes()
139172
return tools.get_assemblies(species_name, genomes, exact)
140173

174+
@app.get("/tracks/{genome}")
175+
def list_tracks_api(genome: str, timeout: int = Query(10, description="Request timeout in seconds")):
176+
"""
177+
HTTP endpoint to list all available UCSC genome browser tracks for a given assembly/genome.
178+
179+
Example:
180+
/tracks/hg38
181+
"""
182+
tracks = tools.list_ucsc_tracks(genome, timeout=timeout)
183+
return tracks
184+
141185
@app.post("/refresh-cache")
142186
def refresh_ucsc_cache():
143187
"""Force-refresh UCSC genome cache."""

src/server/tools.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,54 @@ def get_annotations(region: str, genome: str = "hg38", track: str = "knownGene")
4343
return response.json()
4444
except ValueError:
4545
return {"error": "No valid JSON returned", "text": response.text}
46+
47+
def list_ucsc_tracks(genome: str = "hg38", timeout: int = 10) -> Dict[str, Any]:
48+
"""
49+
List all available UCSC genome browser tracks for a given assembly.
50+
51+
Example:
52+
list_ucsc_tracks("hg38")
53+
54+
Returns:
55+
dict: Simplified UCSC track metadata, or {"error": "..."} if something fails.
56+
"""
57+
url = f"{UCSC_BASE}/list/tracks?genome={genome}"
58+
59+
try:
60+
resp = requests.get(url, timeout=timeout)
61+
resp.raise_for_status()
62+
data = resp.json()
63+
64+
# The main track data is nested under the genome key, e.g. data["hs1"]
65+
if genome not in data:
66+
return {"error": f"No track data found for {genome}"}
67+
68+
tracks = data[genome]
69+
70+
# Flatten the response into a list of top-level tracks (filter out composite containers)
71+
simplified_tracks = []
72+
for track_name, track_info in tracks.items():
73+
if isinstance(track_info, dict) and "type" in track_info:
74+
simplified_tracks.append({
75+
"track_id": track_name,
76+
"shortLabel": track_info.get("shortLabel"),
77+
"longLabel": track_info.get("longLabel"),
78+
"type": track_info.get("type"),
79+
"group": track_info.get("group"),
80+
"bigDataUrl": track_info.get("bigDataUrl"),
81+
"html": track_info.get("html"),
82+
})
83+
84+
return {
85+
"genome": genome,
86+
"track_count": len(simplified_tracks),
87+
"tracks": simplified_tracks,
88+
}
89+
90+
except requests.exceptions.RequestException as e:
91+
return {"error": f"Failed to fetch tracks for {genome}: {e}"}
92+
except ValueError:
93+
return {"error": f"Invalid JSON returned for {genome} tracks"}
4694

4795
# ---------------------------------------------------------------------
4896
# UCSC genome listing utilities

0 commit comments

Comments
 (0)