mgbam commited on
Commit
5035006
·
verified ·
1 Parent(s): f62a8d2

Update mcp/ctgov.py

Browse files
Files changed (1) hide show
  1. mcp/ctgov.py +65 -15
mcp/ctgov.py CHANGED
@@ -1,21 +1,71 @@
1
- """ClinicalTrials.gov v2 async helper.
2
- Docs: https://clinicaltrials.gov/api/gui/ref/api_urls#v2
3
- Provides `search_trials_v2` which returns a list of study dicts.
4
  """
 
 
 
 
 
 
 
5
 
6
- import httpx, asyncio
7
- from typing import List, Dict
 
 
8
 
9
- _BASE = "https://clinicaltrials.gov/api/v2/studies"
 
 
 
 
 
 
 
 
 
10
 
11
- async def search_trials_v2(query: str, *, max_n: int = 20) -> List[Dict]:
12
- """Search CT.gov v2 endpoint and return list of studies."""
 
 
 
13
  params = {
14
- "query": query,
15
- "pageSize": max_n,
16
- "fields": "nctId,briefTitle,phase,status,startDate,conditions,interventions",
17
  }
18
- async with httpx.AsyncClient(timeout=15) as client:
19
- resp = await client.get(_BASE, params=params)
20
- resp.raise_for_status()
21
- return resp.json().get("studies", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
 
 
2
  """
3
+ ClinicalTrials.gov helper – v2 primary, v1 fallback.
4
+ """
5
+
6
+ from __future__ import annotations
7
+ import asyncio, httpx, urllib.parse
8
+ from functools import lru_cache
9
+ from typing import List, Dict, Any
10
 
11
+ # v2 REST
12
+ _V2 = "https://clinicaltrials.gov/api/v2/studies"
13
+ # legacy v1 (read-only but still works)
14
+ _V1 = "https://clinicaltrials.gov/api/query/study_fields"
15
 
16
+ _HEADERS = {
17
+ "User-Agent": (
18
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
19
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
20
+ "Chrome/125.0 Safari/537.36"
21
+ ),
22
+ "Accept": "application/json",
23
+ }
24
+ _TIMEOUT = 15
25
+ _MAX = 50 # hard cap
26
 
27
+ async def _get(url: str, params: Dict[str, Any]) -> httpx.Response:
28
+ async with httpx.AsyncClient(timeout=_TIMEOUT, headers=_HEADERS) as cli:
29
+ return await cli.get(url, params=params)
30
+
31
+ async def _try_v2(term: str, limit: int) -> List[Dict]:
32
  params = {
33
+ "query" : term,
34
+ "pageSize": limit,
35
+ "fields" : "nctId,briefTitle,phase,status,startDate,conditions,interventions",
36
  }
37
+ r = await _get(_V2, params)
38
+ if r.status_code == 200:
39
+ return r.json().get("studies", [])
40
+ if r.status_code in {403, 429, 500, 503}:
41
+ raise RuntimeError("v2 unavailable")
42
+ r.raise_for_status() # other 4xx
43
+
44
+ async def _try_v1(term: str, limit: int) -> List[Dict]:
45
+ params = {
46
+ "expr" : term,
47
+ "fields" : "NCTId,BriefTitle,Phase,OverallStatus,StartDate,Condition,InterventionName",
48
+ "max_rnk" : limit,
49
+ "min_rnk" : 1,
50
+ "fmt" : "json",
51
+ }
52
+ r = await _get(_V1, params)
53
+ r.raise_for_status()
54
+ return r.json().get("StudyFieldsResponse", {}).get("StudyFields", [])
55
+
56
+ @lru_cache(maxsize=512)
57
+ async def search_trials(term: str, *, max_studies: int = 10) -> List[Dict]:
58
+ """
59
+ Return up to *max_studies* trial dicts; v2 first, v1 fallback.
60
+ """
61
+ limit = max(1, min(max_studies, _MAX))
62
+ # v2 with back-off: 2s → 4s
63
+ for delay in (0, 2, 4):
64
+ try:
65
+ if delay:
66
+ await asyncio.sleep(delay)
67
+ return await _try_v2(term, limit)
68
+ except RuntimeError:
69
+ continue
70
+ # fallback to legacy endpoint
71
+ return await _try_v1(term, limit)