Skip to main content
Glama
vikasgaddu1

CRF Annotation MCP Server

by vikasgaddu1
README.md
# CRF Annotation MCP Server

A Model Context Protocol (MCP) server that enables AI assistants to query SDTM annotations from annotated CRF PDFs.

## Prerequisites

Before you begin, ensure you have:

- **Python 3.10 or higher** — Check with `python --version` or `python3 --version`
- **uv** (recommended) or **pip** — [uv](https://docs.astral.sh/uv/) is a fast Python package manager. Install with:
  - **Windows (PowerShell):** `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"`
  - **macOS/Linux:** `curl -LsSf https://astral.sh/uv/install.sh | sh`
- **An annotated CRF PDF** — Or use the sample file included in `examples/acrf.pdf` (FreeText annotations; the format used in real annotated CRFs)

## Features

- **Read annotated CRF PDFs** - Extract SDTM domain/variable annotations from PDF comments
- **Domain-aware queries** - Automatically includes SUPP domains when querying parent domains
- **Natural language interface** - Ask questions like "Give me all annotations for DM domain"
- **SDTM-compliant** - Understands SDTM domain relationships and naming conventions
- **MCP integration** - Works with any MCP-compatible AI assistant (Claude Desktop, Cline, etc.)

## Use Cases

- "Show me all DM annotations" → Returns DM + SUPPDM annotations
- "What CRF pages contain adverse event data?" → Returns AE domain page references
- "List all variables annotated for vital signs" → Returns VS annotations
- "Which domains are annotated on page 15?" → Cross-reference page to domains

## Architecture

```
crf-annotation-mcp/
├── .cursor/
│   └── mcp.json               # MCP config (create this for Cursor)
├── docs/
│   ├── SETUP_GUIDE.md         # Detailed setup instructions
│   └── QUICKSTART.md          # 5-minute setup guide
├── examples/
│   ├── acrf.pdf               # Sample annotated CRF (FreeText format)
│   └── example_usage.py
├── src/
│   └── crf_annotation_mcp/
│       ├── server.py          # MCP server implementation
│       ├── parser.py          # PDF annotation extraction
│       ├── query.py           # Annotation query engine
│       └── models.py          # Data models
├── tests/
├── README.md
└── pyproject.toml
```

## Installation

### Step 1: Clone the repository

```bash
git clone https://github.com/vikasgaddu1/crf-annotation-mcp.git
cd crf-annotation-mcp
```

### Step 2: Install the project

**Option A: Using uv (recommended)**

If you have [uv](https://docs.astral.sh/uv/) installed, you can skip creating a venv — `uv run` handles it automatically:

```bash
uv pip install -e .
```

**Option B: Using pip with a virtual environment**

Using a virtual environment avoids conflicts with other Python packages (fastapi, jupyter, streamlit, etc.):

```bash
# Create virtual environment
python -m venv .venv

# Activate it
# Windows (PowerShell):
.\.venv\Scripts\Activate.ps1

# macOS/Linux:
source .venv/bin/activate

# Install the project
pip install -e .
```

### Step 3: Verify installation

Run the server to confirm it works (it will exit with an error about `CRF_PDF_PATH` — that's expected):

```bash
# With uv:
uv run crf-annotation-mcp

# With pip (after activating venv):
crf-annotation-mcp
```

You should see: `CRF_PDF_PATH environment variable not set`. That means the server is installed correctly.

### Troubleshooting: Dependency Conflicts

If you see errors about `starlette`, `anyio`, or `packaging` version conflicts when installing, you're likely using a global Python with other packages. **Use a virtual environment** (Option B above) to isolate this project's dependencies.

## Configuration

### Quick Start: Cursor (First-Time Setup)

If you're using Cursor and want to get started quickly:

1. **Complete Installation** (see above) — clone the repo and run `uv pip install -e .` or `pip install -e .`

2. **Create the MCP config file** — Create a file at `.cursor/mcp.json` in the project root (same folder as `pyproject.toml`):

   ```
   crf-annotation-mcp/
   ├── .cursor/
   │   └── mcp.json    ← Create this file
   ├── pyproject.toml
   └── ...
   ```

3. **Add this configuration** to `.cursor/mcp.json` (replace paths with your actual paths):

   **Windows:**
   ```json
   {
     "mcpServers": {
       "crf-annotations": {
         "command": "uv",
         "args": [
           "--directory",
           "C:\\path\\to\\crf-annotation-mcp",
           "run",
           "crf-annotation-mcp"
         ],
         "env": {
           "CRF_PDF_PATH": "C:\\path\\to\\crf-annotation-mcp\\examples\\acrf.pdf"
         }
       }
     }
   }
   ```

   **macOS/Linux:**
   ```json
   {
     "mcpServers": {
       "crf-annotations": {
         "command": "uv",
         "args": [
           "--directory",
           "/path/to/crf-annotation-mcp",
           "run",
           "crf-annotation-mcp"
         ],
         "env": {
           "CRF_PDF_PATH": "/path/to/crf-annotation-mcp/examples/acrf.pdf"
         }
       }
     }
   }
   ```

4. **Restart Cursor completely** — Close and reopen Cursor (or File → Exit, then reopen). MCP config changes require a full restart.

5. **Verify it works** — Open a new chat in Cursor. The Agent should now have access to CRF annotation tools. Try asking: *"List all domains in the annotated CRF"* or *"What annotations are in the DM domain?"* You can also check **Settings → Tools & MCP** (Ctrl+Shift+J) to see if `crf-annotations` is listed and enabled.

**Usage tip:** You don't need to say "use MCP" — the Agent picks up tools automatically. For CRF-specific queries, phrasing like *"What VS variables are annotated in my CRF?"* or *"Query the annotations for the EX domain"* helps the Agent use the right tools.

**Don't have uv?** Install it (see Prerequisites) or use your venv's Python. If you used `pip install -e .` in a virtual environment, use this config instead (replace paths):

   ```json
   {
     "mcpServers": {
       "crf-annotations": {
         "command": "C:\\path\\to\\crf-annotation-mcp\\.venv\\Scripts\\python.exe",
         "args": ["-m", "crf_annotation_mcp.server"],
         "env": {
           "CRF_PDF_PATH": "C:\\path\\to\\your\\annotated_crf.pdf"
         }
       }
     }
   }
   ```

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "crf-annotations": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/crf-annotation-mcp",
        "run",
        "crf-annotation-mcp"
      ],
      "env": {
        "CRF_PDF_PATH": "/path/to/your/annotated_crf.pdf"
      }
    }
  }
}
```

### Cursor (Alternative: Global config)

You can also add the server to `~/.cursor/mcp.json` (in your home directory) for all projects. See [Quick Start: Cursor](#quick-start-cursor-first-time-setup) above for the config format.

### Cline (VSCode Extension)

Add to VSCode settings (`settings.json`):

```json
{
  "cline.mcpServers": {
    "crf-annotations": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/crf-annotation-mcp",
        "run",
        "crf-annotation-mcp"
      ],
      "env": {
        "CRF_PDF_PATH": "/path/to/your/annotated_crf.pdf"
      }
    }
  }
}
```

### Continue (VSCode Extension)

Add to `~/.continue/config.json`:

```json
{
  "mcpServers": [
    {
      "name": "crf-annotations",
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/crf-annotation-mcp",
        "run",
        "crf-annotation-mcp"
      ],
      "env": {
        "CRF_PDF_PATH": "/path/to/your/annotated_crf.pdf"
      }
    }
  ]
}
```

### Zed Editor

Add to Zed settings (Settings → Assistant → MCP):

```json
{
  "assistant": {
    "version": "2",
    "provider": {
      "name": "anthropic",
      "mcpServers": {
        "crf-annotations": {
          "command": "uv",
          "args": [
            "--directory",
            "/path/to/crf-annotation-mcp",
            "run",
            "crf-annotation-mcp"
          ],
          "env": {
            "CRF_PDF_PATH": "/path/to/your/annotated_crf.pdf"
          }
        }
      }
    }
  }
}
```

**Note:** Replace `/path/to/crf-annotation-mcp` with the actual installation path and `/path/to/your/annotated_crf.pdf` with your PDF file path.

### Which CRF does the MCP use?

The MCP uses **one CRF at a time**, chosen by the `CRF_PDF_PATH` environment variable in your mcp.json. There is no default — whatever path you set is the CRF the server loads.

- **PDF location:** The PDF can be **anywhere** on your system. It does not need to be in the `examples/` folder. Use `acrf.pdf` as the reference — it uses FreeText annotations (the standard format for annotated CRFs), not sticky comments.
- **Multiple CRFs:** To switch to a different CRF, change `CRF_PDF_PATH` in `.cursor/mcp.json` to the new file path, then **restart Cursor** so the MCP server reloads with the new path. You cannot query multiple CRFs in the same session.

### Troubleshooting

| Issue | Solution |
|-------|----------|
| **Server doesn't appear in Cursor** | Restart Cursor completely (File → Exit, then reopen). MCP config is loaded on startup. |
| **"CRF_PDF_PATH environment variable not set"** | Add the `env` block with `CRF_PDF_PATH` to your mcp.json. Use the full path to your PDF. |
| **"uv: command not found"** | Install [uv](https://docs.astral.sh/uv/) or use the pip/venv alternative in the Cursor Quick Start section. |
| **Server fails to start** | Check **Output** panel (Ctrl+Shift+U / Cmd+Shift+U) → select "MCP Logs" for error details. |
| **Wrong path on Windows** | Use double backslashes in JSON: `"C:\\Users\\you\\path\\to\\file.pdf"` |

## MCP Tools

### `get_annotations`
Query annotations by domain, page, or variable.

**Parameters:**
- `domain` (optional): SDTM domain (e.g., "DM", "AE", "VS", "EX")
- `page` (optional): CRF page number
- `variable` (optional): Variable name (e.g., "USUBJID", "AEDECOD")
- `include_supp` (default: true): Include SUPP domain annotations

**Examples:**
```python
# Get all DM annotations (includes SUPPDM automatically)
get_annotations(domain="DM")

# Get annotations on page 5
get_annotations(page=5)

# Get specific variable across all domains
get_annotations(variable="USUBJID")
```

### `list_domains`
List all SDTM domains found in the annotated CRF.

**Returns:** Array of domain names with annotation counts

### `get_page_mapping`
Get a mapping of CRF pages to SDTM domains.

**Returns:** Dictionary of page numbers to domains

### `search_annotations`
Free-text search across all annotations.

**Parameters:**
- `query`: Search term (e.g., "date of birth", "adverse event")

### `get_domain_relationships`
Get SUPP domain relationships for a parent domain (e.g., DM → SUPPDM).

**Parameters:**
- `domain`: Parent domain (e.g., "DM", "CM", "QS")

## How It Works

1. **Extract annotations** - Reads PDF comments/annotations using PyMuPDF
2. **Parse SDTM metadata** - Identifies domain, variable, and page references
3. **Build query index** - Creates searchable index of annotations
4. **Serve via MCP** - Exposes tools for AI assistants to query

## Annotation Format Expected

The parser supports two annotation formats:

**1. FreeText annotations** (typical for annotated CRFs, as in `acrf.pdf`):
- `DOMAIN=Description` (e.g., `VS=Vital Signs`) — sets domain context
- `VARIABLE` (e.g., `VSTEST`, `SITEID`) — variable under current domain
- `VARIABLE when X = Y` (e.g., `SCORRES when SCTESTCD = SUBJINIT`)
- `VARIABLE / VARIABLE2 when X = Y` (e.g., `VSORRES / VSORRESU when VSTESTCD = SYSBP`)
- `VARIABLE in SUPPDOMAIN` (e.g., `RACEOTH in SUPPDM`)

**2. Sticky-note format** (legacy):
```
DM.USUBJID
DM.RFSTDTC (Page 3, Question 5)
SUPPDM.RACE2 (Page 2, Multi-select option)
AE.AEDECOD (Page 15-18, Free text)
```

## Integration with annoSDTMCheck

This MCP server is designed to complement [annoSDTMCheck](https://github.com/vikasgaddu1/annoSDTMCheck):

- **annoSDTMCheck**: Validates annotations against actual SDTM datasets
- **crf-annotation-mcp**: Makes annotations queryable via AI assistants

Workflow:
1. Annotate your CRF PDF with SDTM mappings
2. Use **crf-annotation-mcp** during study setup to query annotations
3. After dataset creation, use **annoSDTMCheck** to validate

## Roadmap

- [ ] Support for Excel annotation specs (in addition to PDF)
- [ ] Integration with Define.xml for variable metadata
- [ ] Annotation validation (detect malformed annotations)
- [ ] Export query results to Define.xml or spreadsheet
- [ ] Support for ODM-XML CRF annotations

## Contributing

Contributions welcome! See [docs/SETUP_GUIDE.md](docs/SETUP_GUIDE.md) for setup details.

## License

MIT

## Contact

Vikas Gaddu - [@vikasgaddu1](https://github.com/vikasgaddu1)