cdisc-mcp
<div align="center">
<br/>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/header-dark.svg">
<img src="docs/cdisc-mcp.png" alt="CDISC MCP" width="728">
</picture>
<br/>
**CDISC Library MCP Server** β Query clinical data standards (SDTM, ADaM, CDASH, CT) directly from AI assistants.
<br/>
[](https://python.org)
[](https://github.com/jlowin/fastmcp)

[](tests/)
[](https://library.cdisc.org)
<br/>
**π Translations:** [δΈζ README](docs/README.zh.md) Β· [ζ₯ζ¬θͺ README](docs/README.ja.md)
<br/>
</div>
---
## What is This?
The **CDISC MCP Server** connects AI assistants (Claude, VS Code Copilot, Cursor, etc.) to the [CDISC Library REST API](https://library.cdisc.org), exposing 11 structured tools for querying clinical trial data standards. Ask your AI assistant questions like:
> *"What variables are in the SDTM AE domain?"*/m
> *"Show me the ADSL variables in ADaM IG 1.3"*
> *"List all available Controlled Terminology packages"*
For full setup instructions, see the **[User Manual β](docs/user-manual.md)**. See **[Examples β](docs/examples.md)** for real conversation samples.
---
## Quick Start
### 0 Β· One-Line AI Install
Have an AI assistant install everything for you:
```bash
curl -fsSL https://raw.githubusercontent.com/Teninq/cdisc-mcp/main/install.md
```
> Paste the output into Claude, Copilot, or any AI chat β it will read the guide and walk you through the full setup interactively.
---
### 1 Β· Get a CDISC Library API Key
Register at **https://library.cdisc.org** and obtain a personal API key.
### 2 Β· Install
```bash
# Runtime only
pip install -e .
# With dev dependencies
pip install -e ".[dev]"
# With web explorer
pip install -e ".[web]"
```
### 3 Β· Set API Key
```bash
# Linux / macOS
export CDISC_API_KEY=your_key_here
# Windows β Command Prompt
set CDISC_API_KEY=your_key_here
# Windows β PowerShell
$env:CDISC_API_KEY = "your_key_here"
```
### 4 Β· Run
```bash
# Start MCP server (for AI assistant integration)
cdisc-mcp
# OR: Start Web Explorer (quick interactive testing)
python web/app.py
```
---
## Web Explorer β Quick Interactive Testing
The fastest way to verify your setup and explore tools **without any AI client**.
```bash
# 1. Install web dependencies
pip install -e ".[web]"
# 2. Set your API key
export CDISC_API_KEY=your_key_here # Linux/macOS
set CDISC_API_KEY=your_key_here # Windows CMD
$env:CDISC_API_KEY = "your_key_here" # Windows PowerShell
# 3. Start the bridge server
python web/app.py
# 4. Open in browser
# β http://localhost:8080
```
The explorer provides:
- **Sidebar navigation** β all 11 tools organized by standard (SDTM / ADaM / CDASH / Terminology)
- **Auto-generated forms** β dropdowns for versions and domains, text inputs for variables
- **Live JSON responses** β syntax-highlighted, copyable output with response time
- **Bridge status indicator** β confirms your API key and connectivity
> **Tip:** Use version strings with dashes β `3-4` not `3.4`, `1-3` not `1.3`.
> Example: SDTM-IG `3-4`, ADaM-IG `1-3`, CDASH-IG `2-0`
---
## Available Tools
| # | Tool | Standard | Description |
|---|------|----------|-------------|
| 1 | `list_products` | β | List all available CDISC standards and published versions |
| 2 | `get_sdtm_domains` | SDTM | List all datasets in a SDTM-IG version |
| 3 | `get_sdtm_domain_variables` | SDTM | List all variables in an SDTM domain/dataset |
| 4 | `get_sdtm_variable` | SDTM | Get full definition of a specific SDTM variable |
| 5 | `get_adam_datastructures` | ADaM | List all data structures in an ADaM-IG version |
| 6 | `get_adam_variable` | ADaM | Get definition of a specific ADaM variable |
| 7 | `get_cdash_domains` | CDASH | List all domains in a CDASH-IG version |
| 8 | `get_cdash_domain_fields` | CDASH | Get all data collection fields for a CDASH domain |
| 9 | `list_ct_packages` | CT | List all available Controlled Terminology packages |
| 10 | `get_codelist` | CT | Get definition and metadata of a CT codelist |
| 11 | `get_codelist_terms` | CT | List all valid terms in a CT codelist |
### Version Reference
| Standard | Available Versions (use dashes) |
|----------|--------------------------------|
| SDTM-IG | `3-4` Β· `3-3` Β· `3-2` Β· `3-1-3` |
| ADaM-IG | `1-3` Β· `1-2` Β· `1-1` Β· `1-0` |
| CDASH-IG | `2-1` Β· `2-0` Β· `1-1-1` |
---
## Connect to an AI Assistant
### Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"cdisc": {
"command": "cdisc-mcp",
"env": {
"CDISC_API_KEY": "your_key_here"
}
}
}
}
```
### VS Code / Cursor
Add to `.vscode/mcp.json` or equivalent MCP config:
```json
{
"servers": {
"cdisc": {
"command": "cdisc-mcp",
"env": {
"CDISC_API_KEY": "your_key_here"
}
}
}
}
```
### Claude Code (CLI)
Claude Code supports MCP servers at two scopes: **user** (global, all projects) and **project** (local, current project only).
**Option A β CLI command (recommended)**
```bash
# Add globally (available in all projects)
claude mcp add cdisc-mcp -e CDISC_API_KEY=your_key_here -- python -m cdisc_mcp.server
# Add for the current project only
claude mcp add cdisc-mcp --scope project -e CDISC_API_KEY=your_key_here -- python -m cdisc_mcp.server
# Verify the server was registered
claude mcp list
```
**Option B β Edit `~/.claude.json` directly**
```json
{
"mcpServers": {
"cdisc-mcp": {
"command": "python",
"args": ["-m", "cdisc_mcp.server"],
"env": {
"CDISC_API_KEY": "your_key_here"
}
}
}
}
```
> **Tip:** If `CDISC_API_KEY` is already in your system environment, omit the `env` block entirely β Claude Code inherits it automatically.
Once registered, confirm with `/mcp` in any Claude Code session, then use the tools conversationally:
```
User: What SDTM domains are defined in version 3.4?
Claude: [calls get_sdtm_domains with version="3-4"] ...
```
---
## Using in Your Own Python Tools
You can call CDISC MCP tools programmatically from any Python script using the official MCP client SDK.
### Install the client
```bash
pip install mcp
```
### Minimal example
```python
import asyncio
import os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
SERVER = StdioServerParameters(
command="python",
args=["-m", "cdisc_mcp.server"],
env={"CDISC_API_KEY": os.environ["CDISC_API_KEY"]},
)
async def main():
async with stdio_client(SERVER) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List all available tools
tools = await session.list_tools()
print([t.name for t in tools.tools])
# Call a tool
result = await session.call_tool(
"get_sdtm_domains",
arguments={"version": "3-4"},
)
print(result.content[0].text)
asyncio.run(main())
```
### Reusable helper
```python
import asyncio, os, json
from contextlib import asynccontextmanager
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
_SERVER = StdioServerParameters(
command="python",
args=["-m", "cdisc_mcp.server"],
env={"CDISC_API_KEY": os.environ["CDISC_API_KEY"]},
)
@asynccontextmanager
async def cdisc_session():
"""Async context manager that yields an initialised MCP session."""
async with stdio_client(_SERVER) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
yield session
async def call(tool: str, **kwargs) -> dict:
async with cdisc_session() as s:
result = await s.call_tool(tool, arguments=kwargs)
return json.loads(result.content[0].text)
# --- Usage examples ---
async def demo():
# Fetch SDTM domains
domains = await call("get_sdtm_domains", version="3-4")
# Fetch all variables in the AE domain
variables = await call("get_sdtm_domain_variables", version="3-4", domain="AE")
# Look up a single variable
aeterm = await call("get_sdtm_variable", version="3-4", domain="AE", variable="AETERM")
print(aeterm)
asyncio.run(demo())
```
### Available tool signatures (quick reference)
```python
# Products / versions
await session.call_tool("list_products", {})
# SDTM
await session.call_tool("get_sdtm_domains", {"version": "3-4"})
await session.call_tool("get_sdtm_domain_variables", {"version": "3-4", "domain": "AE"})
await session.call_tool("get_sdtm_variable", {"version": "3-4", "domain": "AE", "variable": "AETERM"})
# ADaM
await session.call_tool("get_adam_datastructures", {"version": "1-3"})
await session.call_tool("get_adam_variable", {"version": "1-3", "data_structure": "ADSL", "variable": "USUBJID"})
# CDASH
await session.call_tool("get_cdash_domains", {"version": "2-0"})
await session.call_tool("get_cdash_domain_fields", {"version": "2-0", "domain": "AE"})
# Controlled Terminology
await session.call_tool("list_ct_packages", {})
await session.call_tool("get_codelist", {"package_id": "sdtmct-2024-03-29", "codelist_id": "C66781"})
await session.call_tool("get_codelist_terms", {"package_id": "sdtmct-2024-03-29", "codelist_id": "AGEU"})
```
> **Version format:** Always use dashes, not dots β `"3-4"` not `"3.4"`.
---
## Architecture
```
MCP Client (Claude / VS Code / Cursor)
β
β MCP protocol (stdio)
βΌ
server.py ββββ FastMCP tool registration
β
βΌ
tools/ ββββββββ domain functions (sdtm, adam, cdash, terminology, search)
β
βΌ
client.py βββββ CDISCClient (async HTTP Β· TTL cache Β· retry)
β
β HTTPS
βΌ
library.cdisc.org/api ββββ CDISC Library REST API
```
**Key design decisions:**
- `CDISCClient` is a singleton async HTTP client with 1-hour TTL in-memory cache
- Only `429` and `5xx` responses are retried; `4xx` raise immediately
- Tool functions are pure async β independently testable without patching
- `format_response()` strips HAL `_links` metadata, extracts structured data for LLM consumption
---
## Development
**Development workflow (devel-first, branch & merge SOP):** See [Contributing Guide](docs/CONTRIBUTING.md).
### Running Tests
```bash
# Full suite with coverage (β₯80% required)
pytest
# Specific modules
pytest tests/test_tools.py tests/test_client.py -v
# Single test
pytest tests/test_tools.py::test_list_products -v
```
### Code Quality
```bash
ruff check src/ tests/ # Linting
mypy src/ # Type checking
```
### GitHub Branch Protection (Required Checks)
Configure `main` branch protection to require:
- `CI Tests / tests`
- `CI Lint / lint`
- `CI Types / types`
### Project Structure
```
src/cdisc_mcp/
βββ server.py # FastMCP server + tool registration
βββ client.py # Async HTTP client (cache, retry)
βββ config.py # Config dataclass + env loader
βββ errors.py # AuthenticationError, ResourceNotFoundError, RateLimitError
βββ response_formatter.py # HAL response normalization
βββ tools/
βββ search.py # list_products (product catalog)
βββ sdtm.py # SDTM domain/variable tools
βββ adam.py # ADaM datastructure/variable tools
βββ cdash.py # CDASH domain/field tools
βββ terminology.py # CT package/codelist tools
βββ _validators.py # Path traversal guards
web/
βββ app.py # FastAPI bridge server
βββ index.html # Single-file browser explorer
tests/
βββ test_config.py
βββ test_client.py
βββ test_response_formatter.py
βββ test_tools.py
βββ test_errors.py
βββ test_server.py
```
---
## License
MIT license.
---
<div align="center">
Built for clinical data professionals working with CDISC standards.
**[User Manual](docs/user-manual.md)** Β· **[Examples](docs/examples.md)** Β· **[CDISC Library](https://library.cdisc.org)** Β· **[API Docs](https://www.cdisc.org/cdisc-library/api-documentation)**
</div>
TDQS
Scored across 12 tools
Each tool has a clearly distinct purpose targeting specific CDISC standards (ADaM, CDASH, SDTM, Controlled Terminology) with no overlap. The tools are organized by standard type and operation level (list domains vs. get specific variables), making them easily distinguishable.
All tools follow a consistent verb_noun pattern with clear prefixes indicating the standard (get_adam_, get_cdash_, get_sdtm_, get_codelist_, list_, search_). The naming is highly predictable and follows the same snake_case convention throughout.
12 tools is well-scoped for a CDISC standards lookup server, covering multiple standards (ADaM, CDASH, SDTM, CT) with appropriate granularity. Each tool serves a specific purpose without redundancy, and the count supports comprehensive querying without being overwhelming.
The toolset provides excellent coverage for querying CDISC standards metadata, with list operations for discovery and get operations for detailed information. Minor gaps exist (e.g., no update/create/delete tools, but that's appropriate for a read-only reference server), and the search tool helps bridge any remaining gaps.