Skip to main content
Glama
VladYankovenko

QIDI Studio MCP Server

README.md
# QIDI Studio MCP Server

[![ALPHA](https://img.shields.io/badge/status-ALPHA-red)](https://github.com/QIDITECH)
[![macOS Tested](https://img.shields.io/badge/macOS-tested-success)](https://github.com/QIDITECH)
[![Windows](https://img.shields.io/badge/Windows-not%20tested-lightgrey)](https://github.com/QIDITECH)
[![Linux](https://img.shields.io/badge/Linux-not%20tested-lightgrey)](https://github.com/QIDITECH)

**MCP server for managing QIDI Studio 3D printing profiles locally.**

[Русская версия → README_RU.md](README_RU.md)

Connects AI assistants (Claude, Cursor, etc.) directly to your local QIDI Studio configuration files. Reads and writes filament, process, and machine presets via the Model Context Protocol.

> **⚠️ ALPHA VERSION — USE AT YOUR OWN RISK**
> The author is **not responsible** for lost or corrupted presets. Always backup before using MCP.

---

## Why this exists

**Real-time printer control** — start, pause, heat — is easier done by hand in QIDI Studio. MCP is not needed there.

**Print settings, however, are a completely different story.**

QIDI Studio contains hundreds of parameters: temperatures, retractions, fan speeds, support patterns, gaps, accelerations. Every material (PETG, PLA, ABS, composites) and every nozzle requires its own balance. Memorizing all of this and not missing a detail is difficult even for an experienced user.

This MCP solves exactly that: **it helps AI help you pick, create, and save the right preset** — safely, controllably, and with minimal token consumption. You still see the final settings in QIDI Studio before sending the job to print.

### The core problem

- Too many settings across different materials and nozzles
- Easy to forget a key parameter (e.g., `fan_max_speed` for PETG or `support_type` for complex geometry)
- Manually browsing JSON profiles in folders is inconvenient and slow
- No centralized way to "try option A, compare with option B, save the best"

### What the MCP does

- **Auto-detects** the current project from QIDI Studio configs — no need to type printer and nozzle manually
- **Filters** profiles by printer + nozzle + material — you see only what is relevant
- **Safely creates** presets — AI proposes, you confirm, a user preset is saved without overwriting system ones
- **Validates** — checks temperatures, enum values, and unknown keys before writing to disk

---

## ⚠️ Backup First

The server writes directly to QIDI Studio's configuration files. Mistakes happen.

**Where your presets are stored:**

| OS | Path |
|----|------|
| macOS | `~/Library/Application Support/QIDIStudio/user/default/` |
| Windows | `%APPDATA%\QIDIStudio\user\default\` |
| Linux | `~/.config/QIDIStudio/user/default/` |

**Backup:**
```bash
# macOS
cp -r ~/Library/Application\ Support/QIDIStudio/user/default ~/Desktop/QIDIStudio-backup

# Windows
xcopy "%APPDATA%\QIDIStudio\user\default" "%USERPROFILE%\Desktop\QIDIStudio-backup" /E /I

# Linux
cp -r ~/.config/QIDIStudio/user/default ~/QIDIStudio-backup
```

**Restore:**
```bash
# macOS
cp -r ~/Desktop/QIDIStudio-backup/* ~/Library/Application\ Support/QIDIStudio/user/default/

# Windows
xcopy "%USERPROFILE%\Desktop\QIDIStudio-backup" "%APPDATA%\QIDIStudio\user\default" /E /Y

# Linux
cp -r ~/QIDIStudio-backup/* ~/.config/QIDIStudio/user/default/
```

---

## Quick Start

### Requirements
- Python 3.10+
- QIDI Studio installed (the server reads its built-in profiles)

### Install

```bash
git clone https://github.com/QIDITECH/QIDIStudio-MCP.git
cd QIDIStudio-MCP
pip install -r requirements.txt
```

### Connect to Claude Desktop

**Option 1: Automatic (uv)**
```bash
uv run mcp install mcp_server.py --name "QIDI Studio"
```

**Option 2: Manual**

Edit Claude Desktop settings:
- macOS: `~/Library/Application Support/Claude/settings.json`
- Windows: `%APPDATA%\Claude\settings.json`

```json
{
  "mcpServers": {
    "qidi-studio": {
      "command": "python",
      "args": ["/path/to/QIDIStudio-MCP/mcp_server.py"]
    }
  }
}
```

### Environment Variables

| Variable | Purpose | Example |
|----------|---------|---------|
| `QIDI_STUDIO_CONFIG_PATH` | Override user presets path | `~/Library/Application Support/QIDIStudio` |
| `QIDI_STUDIO_SYSTEM_PATH` | Override system profiles path | `/Applications/QIDIStudio.app/Contents/Resources/profiles` |

---

## AI Agent Instructions (Required)

### Step 1: Detect current project (auto)

```
get_current_project_info()  → {"machine": "Q2 0.4 nozzle", "filament": "Generic PETG...", "process": "0.20mm Standard @Q2"}
```

If a project is detected, **propose using the detected printer** instead of asking. Still confirm with the user.

### Step 2: If no project detected, ask the user

Show **numbered lists** and ask to pick by number. Use **compact=True** and **limit=10** to keep responses tiny:

```
Detected printers:
1. Q2
2. X-Plus 4
3. X-Max 4
Which printer? (enter number):

Nozzles for Q2:
1. 0.2
2. 0.4
3. 0.6
4. 0.8
Which nozzle? (enter number):
```

> **Filter semantics for process profiles:** `0.20mm Standard @Q2` has no nozzle in its name. It matches `printer_model="Q2"` + `nozzle_size="0.4"` **only if** its `compatible_printers` contains `"Q2 0.4 nozzle"`. Without `compatible_printers` or for a non-existent nozzle, the profile is filtered out. Always pass both printer and nozzle for process queries.
>
> **list_profiles response format:** On success, an array of profiles is returned. On empty filtered result, an object `{"profiles": [], "debug": {...}, "hint": "..."}` is returned. Check the response type: array = results, object with `profiles` = empty + reasons.
>
> **Debug on empty results:** The `debug` field contains `total_available`, `sample_names`, `name_matches`, `fallback_matches`, and `excluded_examples` — use this to understand why the filter dropped profiles.

> **Filter semantics for material_type:** Only applies to filament profiles. For process, use `printer_model` instead.

### Step 3: Always confirm before creating/updating

```
I will create preset "Glass PETG @Qidi Q2 0.4" with:
- Printer: Q2 (0.4 nozzle)
- Base: Generic PETG @Qidi Q2 0.4 nozzle
- Settings: nozzle_temperature=["255"], fan_max_speed=["0"]
Confirm? (yes/no)
```

### NEVER do this

```
❌ list_profiles("filament", "all")          → returns 1000+ profiles
❌ list_profiles("filament", "all", material="PLA") → still hundreds across all printers
```

### Correct token-efficient flow

```
get_current_project_info()                → auto-detect or skip
get_available_printers()                  → if not detected
get_available_nozzles("Q2")               → if not detected
get_available_vendors()                   → ["Generic", "QIDI", "HATCHBOX"]
get_available_materials("Q2", "0.4")     → ["PETG", "PLA"]
list_profiles("filament", "all", "Q2", "0.4", "PETG", 50, 0, True)  → 3 compact profiles
read_profile(...)                         → inspect one
// CONFIRM with user before:
create_preset(...)                        → create custom
```

---

## MCP Tools

### Discovery (call these first, always filtered)

| Tool | Purpose |
|------|---------|
| `get_current_project_info()` | Auto-detect active printer/filament/process from QIDI Studio config |
| `get_available_printers()` | Printer models detected from local profiles |
| `get_available_nozzles(printer)` | Nozzle sizes for a specific printer |
| `get_available_vendors()` | Filament brands: Generic, QIDI, HATCHBOX, etc. |
| `get_available_materials(printer, nozzle)` | Materials for a printer+nozzle combo |
| `get_available_process_qualities(printer, nozzle)` | Quality presets for a printer+nozzle |

### Profile Operations

| Tool | Purpose |
|------|---------|
| `discover_settings(type, prefix, keys_only)` | Settings with prefix filter and keys-only mode to save tokens |
| `list_profiles(type, source, printer, nozzle, material, limit, offset, compact)` | **FILTERED** list — never omit filters |
| `read_profile(type, name, source)` | Read a profile's JSON |
| `create_preset(type, name, base, settings)` | Create user preset inheriting from base (only overrides passed keys) |
| `update_preset(type, name, settings)` | Update existing user preset (only passed keys) |
| `clone_preset(type, source, new_name)` | Full copy with resolved inheritance |
| `delete_preset(type, name)` | Remove user preset |
| `compare_profiles(type, a, b, max_diffs)` | Show differences (limit output) |
| `validate_preset_settings(type, settings, strict)` | Safety check + enum validation |
| `get_version_info()` | Detected QIDI Studio version |
| `get_config_directory()` | User config path |

---

## Token-Saving Design

This MCP is designed so that the AI agent spends minimum tokens on routine and maximum on decision-making.

- **Settings are read from local JSON profiles directly** — no hardcoded or outdated lists. `discover_settings` returns only what actually exists in the QIDI Studio profiles on your machine.
- **Filtering cuts 90%+ noise** — `list_profiles` with `printer_model`, `nozzle_size`, and `material_type` returns 3–15 profiles instead of 1000+.
- **Compact mode** — `compact=True` returns only name and source, no paths or metadata.
- **Prefix filtering for settings** — `discover_settings("process", "support_", ...)` returns dozens of fields instead of hundreds.
- **Keys-only mode** — `keys_only=True` gives a list of parameter names without values when you just need to know what is available.
- **Pagination and limits** — `limit=50` and `offset` prevent accidental dumps of the entire catalog.
- **Debug on empty results** — if a filter returns `[]`, the response includes `debug` with `excluded_examples` explaining why profiles were dropped. No guessing needed.

---

## Examples

### Create Glass PETG preset
```json
get_available_printers()
get_available_nozzles("Q2")
get_available_vendors()
get_available_materials("Q2", "0.4")
list_profiles("filament", "all", "Q2", "0.4", "PETG", 50, 0, True)
create_preset("filament", "Glass PETG @Qidi Q2 0.4", "Generic PETG @Qidi Q2 0.4 nozzle",
  '{"nozzle_temperature": ["255"], "fan_max_speed": ["0"]}')
```

### Vase mode process
```json
get_available_printers()
get_available_nozzles("Q2")
get_available_process_qualities("Q2", "0.4")
list_profiles("process", "all", "Q2", "0.4", "", 10, 0, True)
clone_preset("process", "0.12mm High Quality @Q2", "0.12mm Vase @Q2", "system")
update_preset("process", "0.12mm Vase @Q2", '{"spiral_mode": "1", "wall_loops": "1"}')
```

### Find support-related settings
```json
discover_settings("process", "support_", False)
discover_settings("process", "", True)  # just key names
```

---

## Testing

```bash
python test_server.py
```

---

## Project Structure

```
QIDIStudio-MCP/
├── mcp_server.py           # MCP server (tools)
├── qidi_config_manager.py  # Local profile manager
├── test_server.py          # Test suite
├── requirements.txt        # Dependencies (mcp)
├── .gitignore
├── README.md               # This file (EN)
└── README_RU.md            # Russian version
```

---

## Tested Platforms

| Platform | Status |
|----------|--------|
| macOS | ✅ Tested (primary) |
| Windows | ⚠️ Not tested |
| Linux | ⚠️ Not tested |

Please open an issue if you test on Windows or Linux.

---

## License

MIT