Initial Ct Msp Mcp Server — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Initial Ct Msp Mcp Server (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A production-quality MCP (Model Context Protocol) server that wraps the ClinicalTrials.gov v2 API. Designed for the OncoHub Tumor Council as the data source for clinical_trial_matching_module v2 / A2.3.
What this server does: structured, TTL-cached access to CT.gov trial data (worldwide + Turkey) via 4 MCP tools. What it does not do: clinical eligibility assessment, scoring, or any form of clinical judgment — that is the consuming system's responsibility.
httpx + asyncio give clean async retry/backoffCT-MSP/
├── server.py # FastMCP server + 4 MCP tools (entry point)
├── ctgov_client.py # Async httpx client, rate limiting, retry
├── normalize.py # Raw CT.gov JSON → flat OncoHub schema
├── cache.py # SQLite TTL cache (no external deps)
├── models.py # Pydantic models (schema documentation)
├── tests/
│ ├── conftest.py # Shared fixtures + sample API responses
│ ├── test_normalize.py
│ ├── test_client.py
│ ├── test_cache.py
│ ├── test_tools.py # MCP tool integration tests (mocked)
│ └── test_smoke.py # Live API smoke tests (opt-in)
├── Dockerfile
├── render.yaml # One-click Render deployment
├── pyproject.toml
└── .env.exampleRequirements: Python 3.11+, pip
# 1. Clone / navigate to project directory
cd CT-MSP
# 2. Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -e ".[dev]"
# 4. Configure environment
cp .env.example .env
# Edit .env if needed (defaults work for local development)
# 5. Run the server
python server.py
# Server starts on http://localhost:8000
# MCP endpoint: http://localhost:8000/mcpnpx @modelcontextprotocol/inspector http://localhost:8000/mcpThis opens a browser UI where you can call tools interactively.
pytest # all unit tests (no network)
pytest -m smoke # live CT.gov smoke tests (requires internet)
pytest tests/test_normalize.py # specific moduleRequired for claude.ai connector — claude.ai only connects to public HTTPS endpoints.
render.yaml — click Apply.https://ctgov-mcp-oncohub.onrender.com (or your custom name)./tmp is ephemeral. Upgrade to Starter ($7/mo) for a persistent disk (/data).Persistent disk (Starter plan): uncomment the disk: section in render.yaml and set CACHE_PATH=/data/ctgov.db.
# Install Railway CLI
npm i -g @railway/cli
railway login
railway init
railway upSet environment variables via railway variables set STATUS_TTL_DAYS=7 ...
fly launch # detects Dockerfile automatically
fly deploy
fly secrets set STATUS_TTL_DAYS=7 META_TTL_DAYS=30Add a volume for persistent cache:
fly volumes create ctgov_cache --size 1Then set CACHE_PATH=/data/ctgov.db in fly.toml.
docker build -t ctgov-mcp .
docker run -p 8000:8000 \
-e STATUS_TTL_DAYS=7 \
-v ctgov_cache:/data \
-e CACHE_PATH=/data/ctgov.db \
ctgov-mcpPrerequisites: - Server running at a public HTTPS URL (e.g. https://ctgov-mcp-oncohub.onrender.com) - claude.ai Pro, Team, or Enterprise plan - OncoHub Project must be privateClinicalTrials.gov (OncoHub)https://<your-host>/mcpdual_source_search) for the module to call them automatically during report generation.Note: claude_desktop_config.json (STDIO transport) does NOT work with claude.ai Projects — only remote HTTP/SSE connectors work there.dual_source_search ← Primary council entry pointSearches worldwide AND Turkey in two sequential calls, merges by NCT ID, marks has_turkey_site.
| Parameter | Type | Default | Description |
|---|---|---|---|
condition | str | required | Disease/condition (maps from A1 tumor field) |
term | str? | null | Biomarker / keyword (maps from A1 biomarker field) |
intervention | str? | null | Drug or target (maps from A1 treatment field) |
statuses | list[str]? | RECRUITING, NOT_YET_RECRUITING | Overall status filter |
phases | list[str]? | null | Phase filter (maps from A1 treatment line) |
page_size | int | 20 | Results per sub-query (max 50) |
force_refresh | bool | false | Skip cache |
Returns: {studies: [...], total_count, turkey_count, next_page_token, note}
search_trialsParameterized worldwide search.
| Parameter | Type | Default | Description |
|---|---|---|---|
condition | str | required | Disease/condition |
term | str? | null | Free-text biomarker or keyword |
intervention | str? | null | Drug or target |
statuses | list[str]? | RECRUITING, NOT_YET_RECRUITING | See valid values below |
phases | list[str]? | null | See valid values below |
country | str? | null | Country name for location filter |
page_size | int | 20 | Max 50 recommended |
page_token | str? | null | Pagination cursor |
force_refresh | bool | false | Skip cache |
Valid `statuses`: RECRUITING, NOT_YET_RECRUITING, ACTIVE_NOT_RECRUITING, COMPLETED, TERMINATED, WITHDRAWN, SUSPENDED, ENROLLING_BY_INVITATION, UNKNOWN
Valid `phases`: EARLY_PHASE1, PHASE1, PHASE2, PHASE3, PHASE4, NA
search_turkey_trialsConvenience wrapper: search_trials with country="Turkey". Same parameters (minus country).
get_trialFetch full details for a single study.
| Parameter | Type | Description |
|---|---|---|
nct_id | str | NCT number, e.g. "NCT05678901" |
force_refresh | bool | Skip cache |
Returns: single normalized study with full eligibility.criteria_text and all locations.
Every tool returns studies in this flat schema:
{
"nct_id": "NCT05678901",
"title": "A Study of Sotorasib...",
"status": "RECRUITING",
"status_unknown_flag": false,
"phases": ["PHASE2"],
"study_type": "INTERVENTIONAL",
"conditions": ["Non-Small Cell Lung Cancer"],
"interventions": [{"type": "DRUG", "name": "Sotorasib"}],
"eligibility": {
"criteria_text": "Inclusion Criteria:\n- KRAS G12C...",
"sex": "ALL",
"min_age": "18 Years",
"max_age": "N/A",
"healthy_volunteers": false
},
"locations": [
{"country": "Turkey", "city": "Istanbul", "facility": "IUH", "status": "RECRUITING"}
],
"has_turkey_site": true,
"turkey_sites": [{"city": "Istanbul", "facility": "IUH", "status": "RECRUITING"}],
"lead_sponsor": "Amgen",
"last_update_post_date": "2024-05-01",
"url": "https://clinicaltrials.gov/study/NCT05678901",
"retrieved_at": "2026-06-24T10:30:00+00:00",
"freshness": "live",
"cached_at": null
}Key fields for clinicians:
status_unknown_flag: true → status unverified for 2+ years, verify before actingretrieved_at → when data was fetched from CT.gov (always surface this)freshness: "cached" → data served from local cache; cached_at shows when it was stored| A1 Patient Profile Field | dual_source_search Parameter | Notes |
|---|---|---|
| Tumor type / primary diagnosis | condition | Required |
| Biomarker (e.g. KRAS G12C, PD-L1) | term | Free text, forwarded to query.term |
| Current/target drug | intervention | Forwarded to query.intr |
| Treatment line / phase preference | phases | e.g. ["PHASE2","PHASE3"] |
| Eligibility status filter | statuses | Default: RECRUITING + NOT_YET_RECRUITING |
Module flow:
A1 Patient Card
↓
A2.3 dual_source_search(condition, term, intervention, phases)
↓
Normalized studies[] with has_turkey_site, freshness, retrieved_at
↓
A3 Eligibility Matrix
↓
A5 Council Report OutputThe server provides data only. A3 performs eligibility matching; A5 formats the output.
| Variable | Default | Description |
|---|---|---|
STATUS_TTL_DAYS | 7 | Max age for cached recruiting/status data |
META_TTL_DAYS | 30 | Reserved for future eligibility-only caching |
CACHE_PATH | .cache/ctgov.db | SQLite file path |
Why TTL matters for patient safety: Trial status changes frequently. A "RECRUITING" status cached 30 days ago may no longer be accurate. STATUS_TTL_DAYS=7 ensures clinicians see data verified within one week, or the server fetches fresh data.
Cache behavior:
freshness: "cached" + cached_at timestampfreshness: "live"force_refresh: true: always fetches live, updates cacheCloud deployments: On free-tier hosts with ephemeral storage, the cache resets on each restart — all requests go live until the cache warms up again. Use a persistent volume for production.
CTGOV_RATE_INTERVAL=1.0)pageSize capped at 50 per callUser-Agent header identifies this client to CT.govCT.gov v2 is a public API — no API key, no OAuth. This server also has no auth on its /mcp endpoint (it serves read-only public data). For production deployments where you want to restrict access, place a reverse proxy with IP allowlisting or a simple bearer token in front.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.