# DICOM MCP Server — Usage Guide

Detailed usage examples, tool reference, and QA workflow patterns for the DICOM MCP server.

For installation and setup, see [INSTALL.md](../INSTALL.md). For project overview, see [README.md](../README.md).

> **Tip: Pair with a filesystem MCP server for maximum capability.** The DICOM MCP server works well on its own for metadata inspection and validation. However, if Claude also has access to a filesystem MCP server with media reading support (e.g. the `read_media_file` tool), it can *view* rendered images directly, enabling an interactive visual feedback loop — render an image, inspect it, adjust ROI placement, measure, iterate — all within a single conversation. Without a filesystem MCP, Claude can still render images and save them to disk, but you'll need to open them yourself in a viewer and describe what you see.

---

## Quick Examples

### Finding test data

```
List all Dixon sequences in my test data directory
```

The server will use `dicom_find_dixon_series` to locate all Dixon sequences and show what image types are available.

### Comparing fat/water selection

```
Compare the headers of these two files to see which Dixon images were selected:
- /data/test01/water.dcm
- /data/test01/fat.dcm
```

### Validating a protocol

```
Validate that /data/scan01/image001.dcm matches our Siemens protocol:
- TR should be 4.5
- TE should be 2.3
- Manufacturer should be Siemens
```

### Quick directory overview

```
Summarize what's in the /data/large_study directory
```

### Finding specific files

```
Find all files in /data/study where the series description contains "MOST"
and echo time is greater than 10
```

### Rendering an image

```
Render /data/liver_scan/slice_005.dcm with auto-windowing
```

### Measuring SNR

```
Compute the SNR on /data/scan.dcm with a signal ROI at [100, 200, 50, 50]
in the liver and a noise ROI at [20, 20, 40, 40] in background air
```

### Dumping full DICOM structure

```
Dump the full DICOM tree of /data/scan.dcm including nested sequences
```

### Comparing UIDs across directories

```
Compare the SeriesInstanceUIDs between /data/study_v1 and /data/study_v2
```

### Verifying segmentation references

```
Check that all segmentation files in /data/segs reference valid source images
```

### Analysing MOLLI inversion times

```
Analyze the inversion times in /data/molli_series — is it a proper 5(3)3 scheme?
```

### Inspecting Philips private tags

```
List all Philips Private Creator tags in /data/philips_scan/image001.dcm
```

```
Look up DD 001 element offset 0x85 in /data/philips_scan/image001.dcm
```

---

## PII Filtering

When PII filtering is enabled (via `DICOM_MCP_PII_FILTER=true`), the following patient tags are replaced with `[REDACTED]` in tool output:

- PatientName
- PatientID
- PatientBirthDate
- PatientSex

**Affected tools**: `dicom_get_metadata`, `dicom_compare_headers`, `dicom_summarize_directory`, `dicom_query`.

All other tools do not expose patient data and are unaffected. See [INSTALL.md](../INSTALL.md) for configuration instructions.

---

## Tool Reference

### Directory & File Discovery

#### `dicom_list_files`

List all DICOM files in a directory with metadata filtering.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path to search |
| `recursive` | bool | `true` | Search subdirectories |
| `filter_sequence_type` | string | `null` | Filter by type: `dixon`, `t1_mapping`, `multi_echo_gre`, `spin_echo_ir`, `t1`, `t2`, `flair`, `dwi`, `localizer` |
| `count_only` | bool | `false` | Return series breakdown with file counts instead of individual file listing |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory": "/data/test_suite",
  "filter_sequence_type": "dixon",
  "count_only": true
}
```

#### `dicom_summarize_directory`

Get a high-level overview of DICOM directory contents showing unique values and file counts for patient info, manufacturer, scanner model, field strength, institution, series descriptions, and sequence types.

When PII filtering is enabled, patient tags (Name, ID, DOB, Sex) are redacted.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path to directory |
| `recursive` | bool | `true` | Search subdirectories |
| `include_series_overview` | bool | `true` | Include per-series table with acquisition parameters (TR, TE, TI, FA) |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory": "/data/large_study",
  "include_series_overview": true
}
```

#### `dicom_find_dixon_series`

Find and analyse Dixon (chemical shift) sequences. Automatically identifies Dixon series and detects image types (water, fat, in-phase, out-phase).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path to search |
| `recursive` | bool | `true` | Search subdirectories |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory": "/data/body_comp_study"
}
```

#### `dicom_search`

Find DICOM files matching specific filter criteria using AND logic across multiple filters.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path to directory |
| `filters` | list[string] | *required* | Filter expressions (see syntax below) |
| `recursive` | bool | `true` | Search subdirectories |
| `mode` | string | `summary` | `count`, `paths`, or `summary` |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Filter syntax:**

| Operator type | Examples |
|---------------|----------|
| Text (case-insensitive) | `"SeriesDescription contains MOST"`, `"PatientName is not UNKNOWN"`, `"SeriesDescription starts with Thigh"`, `"SeriesDescription ends with Dixon"` |
| Numeric/symbolic | `"EchoTime > 10"`, `"FlipAngle <= 15"`, `"RepetitionTime = 516"`, `"SeriesNumber != 0"` |
| Presence | `"InversionTime exists"`, `"InversionTime missing"` |

Tags can be keywords (`EchoTime`) or hex codes (`0018,0081`).

```json
{
  "directory": "/data/study",
  "filters": ["SeriesDescription contains MOST", "EchoTime > 10"],
  "mode": "paths"
}
```

---

### Metadata & Validation

#### `dicom_get_metadata`

Extract DICOM header information organised by tag groups. When PII filtering is enabled, patient tags are redacted.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to DICOM file |
| `tag_groups` | list[string] | `null` | Groups to extract: `patient_info`, `study_info`, `series_info`, `image_info`, `acquisition`, `manufacturer`, `equipment`, `geometry`, `pixel_data` |
| `custom_tags` | list[string] | `null` | Additional tags as hex codes, e.g. `["0018,0080"]` |
| `philips_private_tags` | list[dict] | `null` | Philips private tags to resolve (see below) |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Philips private tags:**

For Philips DICOM files, you can resolve private tags inline without using the separate `dicom_query_philips_private` tool. Each entry is a dict with `dd_number` and `element_offset` (and optionally `private_group`, default `0x2005`).

```json
{
  "file_path": "/data/philips_scan.dcm",
  "tag_groups": ["acquisition", "manufacturer"],
  "philips_private_tags": [
    {"dd_number": 1, "element_offset": 133}
  ]
}
```

**Standard example:**

```json
{
  "file_path": "/data/scan.dcm",
  "tag_groups": ["acquisition", "manufacturer"],
  "custom_tags": ["0018,0080", "0018,0081"]
}
```

#### `dicom_compare_headers`

Compare DICOM headers across multiple files side-by-side. When PII filtering is enabled, patient tags are redacted.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_paths` | list[string] | *required* | 2-10 files to compare |
| `tag_groups` | list[string] | `["acquisition", "series_info"]` | Which tag groups to compare |
| `show_differences_only` | bool | `false` | Only show tags that differ |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "file_paths": [
    "/data/test01/water.dcm",
    "/data/test01/fat.dcm",
    "/data/test01/in_phase.dcm"
  ],
  "show_differences_only": true
}
```

#### `dicom_validate_sequence`

Validate MRI sequence parameters against expected values.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | DICOM file to validate |
| `expected_parameters` | dict | `null` | Expected values. Supported keys: `RepetitionTime`, `EchoTime`, `InversionTime`, `FlipAngle`, `ScanningSequence`, `SequenceVariant`, `MRAcquisitionType` |
| `manufacturer` | string | `null` | Expected manufacturer name |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "file_path": "/data/test.dcm",
  "expected_parameters": {
    "RepetitionTime": 4.5,
    "EchoTime": 2.3,
    "InversionTime": 100,
    "FlipAngle": 10
  },
  "manufacturer": "Siemens"
}
```

#### `dicom_analyze_series`

Comprehensive analysis of a complete DICOM series — checks parameter consistency across all files, verifies completeness (no missing instances), and identifies anomalies.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path containing series files |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory": "/data/series_001"
}
```

#### `dicom_query`

Query arbitrary DICOM tags across all files in a directory. Aggregates unique values with file counts. When PII filtering is enabled, patient tags are redacted.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Path to directory |
| `tags` | list[string] | *required* | Tag keywords (e.g. `"EchoTime"`) or hex codes (e.g. `"0018,0081"`) |
| `recursive` | bool | `true` | Search subdirectories |
| `group_by` | string | `null` | Optional tag to group results by (e.g. `"SeriesDescription"`) |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory": "/data/study",
  "tags": ["EchoTime", "FlipAngle"],
  "group_by": "SeriesDescription"
}
```

---

### Structure & Comparison

#### `dicom_dump_tree`

Full hierarchical dump of DICOM structure, including nested sequences (SQ elements) with tree-character formatting. Useful for understanding complex DICOM files, inspecting nested structures (e.g. ReferencedSeriesSequence, SourceImageSequence), and debugging.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to DICOM file |
| `max_depth` | int | `10` | Maximum nesting depth to display |
| `show_private` | bool | `true` | Include private tags in output |
| `response_format` | string | `markdown` | `markdown` or `json` |

When PII filtering is enabled, patient tags are redacted in the tree output. Pixel data `(7FE0,0010)` is always skipped.

```json
{
  "file_path": "/data/scan.dcm",
  "max_depth": 5,
  "show_private": false
}
```

#### `dicom_compare_uids`

Compare UID sets between two DICOM directories. Identifies shared UIDs, UIDs unique to each directory, and reports counts and details.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory1` | string | *required* | First directory to compare |
| `directory2` | string | *required* | Second directory to compare |
| `recursive` | bool | `true` | Search subdirectories |
| `compare_tag` | string | `SeriesInstanceUID` | Tag to compare — keyword (e.g. `StudyInstanceUID`, `SOPInstanceUID`) or hex code (e.g. `0020,000E`) |
| `response_format` | string | `markdown` | `markdown` or `json` |

```json
{
  "directory1": "/data/study_original",
  "directory2": "/data/study_reprocessed",
  "compare_tag": "SOPInstanceUID"
}
```

---

### Segmentation & T1 Mapping

#### `dicom_verify_segmentations`

Validate that segmentation files correctly reference valid source images. Detects segmentation files by SOPClassUID (`1.2.840.10008.5.1.4.1.1.66.4`) or by the presence of `SourceImageSequence`. For each segmentation, checks that every `ReferencedSOPInstanceUID` points to an existing source file in the same directory.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Directory containing segmentation and source files |
| `recursive` | bool | `true` | Search subdirectories |
| `response_format` | string | `markdown` | `markdown` or `json` |

Reports total segmentation files, total references, matched vs unmatched counts, and details for any unmatched references.

```json
{
  "directory": "/data/study_with_segmentations"
}
```

#### `dicom_analyze_ti`

Extract and validate inversion times from MOLLI / T1 mapping sequences. Handles vendor-specific differences automatically:

- **Siemens / GE**: reads the standard `InversionTime` tag `(0018,0082)`
- **Philips**: falls back to private tag DD 006, offset 0x72 in group 2005 (confirmed at `(2005,xx72)`) or DD 001, offset 0x20 in group 2001

Groups by series, sorts by instance number, and computes statistics including gap analysis to detect missing or out-of-range inversion times.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `directory` | string | *required* | Directory containing MOLLI / T1 mapping files |
| `recursive` | bool | `true` | Search subdirectories |
| `gap_threshold` | float | `2500.0` | Flag consecutive TI gaps exceeding this value (ms) |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Output includes per series:**
- Ordered TI list sorted by instance number
- Count of zero-value TIs (typically output maps, not acquisitions)
- Count of non-zero TIs (actual inversions)
- TI range, mean, and median
- Largest consecutive gap, with warning if exceeding the threshold

```json
{
  "directory": "/data/molli_series",
  "gap_threshold": 3000.0
}
```

---

### Philips Private Tags

#### `dicom_query_philips_private`

Query Philips private DICOM tags using DD number and element offset. Philips MRI scanners store proprietary metadata in private tag blocks whose assignments vary across scanners and software versions — this tool resolves them dynamically.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to a Philips DICOM file |
| `dd_number` | int | `null` | DD number to look up (e.g. `1` for "DD 001") |
| `element_offset` | int | `null` | Element offset within the DD block (e.g. `133` for 0x85) |
| `private_group` | int | `0x2005` | DICOM private group to search |
| `list_creators` | bool | `false` | List all Private Creator tags instead of resolving a specific one |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Two usage modes:**

1. **List creators** — discover what private tag blocks are available:
```json
{
  "file_path": "/data/philips_scan.dcm",
  "list_creators": true
}
```

2. **Resolve a specific tag** — look up a known DD number and offset:
```json
{
  "file_path": "/data/philips_scan.dcm",
  "dd_number": 1,
  "element_offset": 133
}
```

**Common Philips DD numbers and offsets (group 2005):**

| DD # | Offset | Description |
|------|--------|-------------|
| 001 | 0x85 | Shim calculation values |
| 001 | 0x63 | Stack ID |
| 004 | 0x00 | MR Series data object |

---

### Pixel Analysis

#### `dicom_read_pixels`

Extract pixel statistics from a DICOM file. Values are rescaled using RescaleSlope and RescaleIntercept when present (standard for Philips, common on Siemens/GE).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to DICOM file |
| `roi` | list[int] | `null` | Region of interest as `[x, y, width, height]` (top-left corner in pixel coordinates). If omitted, statistics cover the entire image. |
| `include_histogram` | bool | `false` | Include a binned histogram of pixel values |
| `histogram_bins` | int | `50` | Number of histogram bins |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Returns**: min, max, mean, standard deviation, median, 5th/25th/75th/95th percentiles, and pixel count.

```json
{
  "file_path": "/data/liver_scan/slice_005.dcm",
  "roi": [100, 200, 50, 50],
  "include_histogram": true,
  "histogram_bins": 20
}
```

#### `dicom_compute_snr`

Compute signal-to-noise ratio from two ROIs in a DICOM image using the single-image method: SNR = mean(signal) / std(noise).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to DICOM file |
| `signal_roi` | list[int] | *required* | Signal region as `[x, y, width, height]` |
| `noise_roi` | list[int] | *required* | Noise/background region as `[x, y, width, height]` |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Tip**: Use `dicom_render_image` with `overlay_rois` first to visualise and verify ROI placement before measuring.

**Note**: Some manufacturers (notably Philips) zero-fill background air outside the reconstruction FOV, which results in zero noise standard deviation and infinite SNR. In such cases, consider using a homogeneous tissue region (e.g. subcutaneous fat or muscle) as the noise ROI instead.

```json
{
  "file_path": "/data/liver_scan/slice_005.dcm",
  "signal_roi": [100, 200, 50, 50],
  "noise_roi": [20, 20, 40, 40]
}
```

#### `dicom_render_image`

Render a DICOM image to PNG with configurable windowing. Optionally overlays labelled ROI rectangles for visual verification.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | string | *required* | Path to DICOM file |
| `output_path` | string | *required* | Path where the PNG will be saved |
| `window_center` | float | `null` | Manual window center |
| `window_width` | float | `null` | Manual window width |
| `auto_window` | bool | `false` | Auto-calculate window from 5th-95th percentile |
| `overlay_rois` | list[dict] | `null` | ROI overlays (see below) |
| `show_info` | bool | `true` | Burn in series description and windowing values |
| `response_format` | string | `markdown` | `markdown` or `json` |

**Windowing priority:**
1. Explicit `window_center` + `window_width` parameters
2. `auto_window=True` → 5th-95th percentile range
3. DICOM header WindowCenter/WindowWidth values
4. Fallback → full pixel range (min to max)

**ROI overlay format:**
```json
{
  "roi": [x, y, width, height],
  "label": "Signal (Liver)",
  "color": "green"
}
```

Available colours: `red`, `green`, `blue`, `yellow`, `cyan`, `magenta`, `white`.

**Full example:**
```json
{
  "file_path": "/data/scan.dcm",
  "output_path": "/data/renders/scan_auto.png",
  "auto_window": true,
  "overlay_rois": [
    {"roi": [100, 200, 50, 50], "label": "Signal", "color": "green"},
    {"roi": [20, 20, 40, 40], "label": "Noise", "color": "red"}
  ]
}
```

**Viewing rendered images:** If Claude also has access to a filesystem MCP server with media reading capability (e.g. `read_media_file`), it can view the rendered PNG directly and use visual feedback to refine ROI placement iteratively — no need to switch to a separate DICOM viewer.

---

## Output Formats

All tools support two output formats via the `response_format` parameter:

**Markdown** (default) — Human-readable with tables, formatting, and visual indicators. Best for conversational use in Claude Desktop.

**JSON** — Machine-readable structured data with consistent schemas. Best for programmatic processing or piping into other tools.

---

## Performance Considerations

### File scanning limits

All directory scanning tools are limited to processing **1000 files** per scan by default. This limit is configurable via the `DICOM_MCP_MAX_FILES` environment variable (see [INSTALL.md](INSTALL.md#environment-variables)). If this limit is reached, a warning indicates results were truncated (and `truncated: true` in JSON output). Narrow your search to a more specific subdirectory if needed.

### Optimisation tips

- Use `dicom_summarize_directory` instead of `dicom_list_files` when you only need an overview
- Use `dicom_list_files` with `count_only: true` for a compact series inventory
- Use `dicom_search` with `mode: "count"` to quickly check match counts before fetching details
- Use `dicom_query` instead of `dicom_get_metadata` on multiple files when you need aggregate tag values
- Set `recursive: false` to scan only the immediate directory
- For pixel tools, use `dicom_render_image` to verify ROI placement before running `dicom_compute_snr`
- Use `dicom_dump_tree` with `show_private: false` to focus on standard DICOM structure
- Use `dicom_compare_uids` to quickly detect differences between study directories without inspecting every file

---

## QA Workflow Examples

### Workflow 1: Dixon Image Selection Investigation

When you suspect incorrect fat/water selection:

1. `dicom_find_dixon_series` — locate the Dixon series
2. `dicom_list_files` with `filter_sequence_type: "dixon"` — see all Dixon files
3. `dicom_compare_headers` on suspected files — focus on ImageType tags
4. `dicom_get_metadata` — extract full headers for documentation

### Workflow 2: Multi-Manufacturer Validation

When testing across GE, Siemens, and Philips:

1. `dicom_summarize_directory` — check patient info consistency and get a quick inventory
2. `dicom_query` with `group_by: "SeriesDescription"` — compare timing parameters across series
3. `dicom_validate_sequence` with manufacturer-specific expected parameters
4. `dicom_compare_headers` — identify parameter variations
5. Document differences for test specification updates

### Workflow 3: Test Dataset Verification

Before running automated tests:

1. `dicom_analyze_series` on each test case directory
2. `dicom_find_dixon_series` to confirm Dixon sequences are present
3. `dicom_validate_sequence` to check protocol compliance
4. `dicom_search` to find files with unexpected parameter values (e.g. `"FlipAngle != 15"`)

### Workflow 4: Image Quality Assessment

For checking signal quality and image rendering:

1. `dicom_render_image` with `auto_window: true` — render the image and visually inspect
2. `dicom_render_image` with `overlay_rois` — place and verify signal/noise ROI positions
3. `dicom_read_pixels` — check pixel value distributions and histograms
4. `dicom_compute_snr` — measure SNR with verified ROI placements
5. Repeat across series or manufacturers to compare image quality

**Note on Philips data:** Background air is often zero-filled, making the traditional background-air SNR method return infinite. Use a homogeneous tissue region (subcutaneous fat, muscle) as the noise proxy instead.

### Workflow 5: MOLLI / T1 Mapping Validation

When verifying T1 mapping acquisitions across vendors:

1. `dicom_analyze_ti` — extract all inversion times, check for proper MOLLI scheme (e.g. 5(3)3 = 11 TI-weighted images + output maps)
2. Check for gap warnings — large gaps between consecutive TIs may indicate missing heartbeats or acquisition failures
3. `dicom_dump_tree` on a representative file — inspect nested sequence structure if TI extraction fails
4. For Philips data: `dicom_query_philips_private` with `dd_number: 6, element_offset: 114` (0x72) — manually verify the private TI tag if needed

### Workflow 6: Segmentation Verification

When validating segmentation files reference the correct source images:

1. `dicom_verify_segmentations` — check all segmentation-to-source references in one pass
2. Review any unmatched references — missing source files or incorrect ReferencedSOPInstanceUIDs
3. `dicom_compare_uids` with `compare_tag: "SOPInstanceUID"` — compare two directories to find missing or extra files

### Workflow 7: Philips Private Tag Investigation

When investigating Philips-specific metadata:

1. `dicom_query_philips_private` with `list_creators: true` — discover available DD blocks
2. `dicom_query_philips_private` with specific `dd_number` and `element_offset` — resolve individual tags
3. Or use `dicom_get_metadata` with the `philips_private_tags` parameter to extract private tags alongside standard metadata in a single call

### Example Screenshot From Claude Desktop

<img src="../img/claude_desktop_example.png" width="623" alt="Claude Desktop session example">
