prusaslicer-mcp 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastien Coutu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: prusaslicer-mcp
3
+ Version: 0.3.1
4
+ Summary: MCP server that lets Claude drive the PrusaSlicer 2.9.x CLI
5
+ Keywords: mcp,model-context-protocol,prusaslicer,3d-printing,slicer,gcode,claude
6
+ Author: Sebastien Coutu
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
17
+ Requires-Dist: lxml>=6.1.3
18
+ Requires-Dist: mcp>=2.1.0
19
+ Requires-Dist: networkx>=3.7
20
+ Requires-Dist: numpy>=2.5.4
21
+ Requires-Dist: pillow>=13.0.0
22
+ Requires-Dist: pydantic>=2.14.3
23
+ Requires-Dist: trimesh>=5.2.1
24
+ Requires-Python: >=3.12
25
+ Project-URL: Homepage, https://github.com/sebastienc/prusaslicer-mcp
26
+ Project-URL: Repository, https://github.com/sebastienc/prusaslicer-mcp
27
+ Project-URL: Issues, https://github.com/sebastienc/prusaslicer-mcp/issues
28
+ Project-URL: Changelog, https://github.com/sebastienc/prusaslicer-mcp/blob/main/CHANGELOG.md
29
+ Description-Content-Type: text/markdown
30
+
31
+ # prusaslicer-mcp
32
+
33
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude (or any MCP client) drive the
34
+ **PrusaSlicer 2.9.x command line**: discover installed printers and profiles, sanity-check and look at a model,
35
+ work out which way up to print it, slice it headlessly with your real presets, and get print time and filament use back.
36
+
37
+ It runs PrusaSlicer as a separate process, so it works while the GUI is open and does not touch it.
38
+ It does **not** control the GUI itself (PrusaSlicer 2.9.x has no remote-control API), and it targets
39
+ **2.9.x only**; PrusaSlicer 3.x is not supported yet.
40
+
41
+ **Status:** early. Seven tools, tested against PrusaSlicer 2.9.7 on macOS. See the [roadmap](ROADMAP.md).
42
+
43
+ *An independent project. It is not affiliated with, endorsed by or supported by Prusa Research. PrusaSlicer and Prusa are trademarks of Prusa Research a.s.*
44
+
45
+ ## Example
46
+
47
+ Once registered with an MCP client, you can ask things like:
48
+
49
+ > "Which printers do I have installed? Check `~/models/bracket.stl` and slice it on the MINI with
50
+ > Prusament PLA at 0.20mm, 25% infill. How long will it take and how much filament does it use?"
51
+
52
+ Behind that, the client calls `list_printers` → `list_profiles` → `inspect_model` → `slice_model` and
53
+ reads back the print time and filament use. `preview_model` and `preview_gcode` let it *look* at the model and at what was sliced, and `suggest_orientation` proposes which way up to print.
54
+
55
+ A scan that will not slice is a different conversation:
56
+
57
+ > "`~/scans/statue.stl` won't slice. What's wrong with it, and which way up should I print it?"
58
+
59
+ Behind that: `inspect_model` reports an open base, `suggest_orientation` with `close_holes` finds the orientation that stands the capped base flat on the bed (and, given your profiles, measures what the supports would cost in each), `preview_model` shows it before anything is sliced, and `slice_model` prints it.
60
+
61
+ ## Tools
62
+
63
+ | Tool | What it does |
64
+ |---|---|
65
+ | `list_printers` | Installed printer models, exact printer profile names, nozzle variants, bed sizes |
66
+ | `list_profiles` | Print (quality) profiles and compatible filament profiles for a printer profile |
67
+ | `inspect_model` | Size, volume, watertightness, open holes (and how many are flat gaps `close_holes` can cap), part count; optional build-volume fit check (via `trimesh`), a warning when a 3MF's stored position is off the bed, and, for a 3MF saved from PrusaSlicer, the profiles and key settings (infill, brim, supports) stored in it |
68
+ | `slice_model` | Slice with `printer_profile` / `print_profile` / `material_profile` (all three, or none for a PrusaSlicer project, which then keeps its own saved settings), optional setting overrides, `center` (plain 3MF files are centred automatically), transforms (`rotate_x/y/z`, `scale`, `scale_to_fit`, `split`, `copies`), and `close_holes` to cap a flat hole such as a scan's missing base; returns G-code path, time, filament, cost, layers, whether the project's or the profiles' settings were used, whether the model was centred, and how many holes were closed |
69
+ | `suggest_orientation` | Ranks the ways to stand a model: its large flat faces, the axes and the orientation as stored, each with the `rotate_x`/`rotate_y` to reach it, its height, how much of it lies on the bed, how much overhang would need support, and whether it rests on a real base. `close_holes` lets a scan's missing base count. Geometry only (near-equal overhang is a tie), unless you also give the three profiles: then each option, at most six, is sliced with supports on and reports its real support filament, filament and print time (slow, one slice each) |
70
+ | `preview_model` | A PNG of a model from iso, front, side or top, or all four on one sheet, with its size and which way is up; shows a Y-up scan lying on its side at a glance. Takes the same `rotate_*`, `scale` and `scale_to_fit` as `slice_model`, and `close_holes`, and draws the model as PrusaSlicer will transform it, so an orientation can be checked before slicing. A pale grid is the bed, level with the model's lowest point, so it is plain which face rests on it |
71
+ | `preview_gcode` | A PNG of sliced G-code: one layer's toolpaths coloured by feature, or front and side silhouettes of everything printed, where an empty stretch shows as a blank band |
72
+
73
+ Profile names must match exactly; use `list_printers` and `list_profiles` to discover them.
74
+
75
+ **Projects.** A 3MF saved from PrusaSlicer carries its own settings (supports, brim, infill...). Slice it with no profiles to keep them; passing profiles makes PrusaSlicer ignore them and use the plain profile values ([ADR 0007](docs/decisions/0007-slice-prusaslicer-projects-with-their-own-settings.md)). `inspect_model` shows what a project saves.
76
+ G-code is always written as text (`--binary-gcode=0`) so statistics can be read back.
77
+
78
+ ## Install
79
+
80
+ Requires Python 3.12+ and PrusaSlicer 2.9.x. The easiest way is [uv](https://docs.astral.sh/uv/), which runs the
81
+ server without a separate install step:
82
+
83
+ ```bash
84
+ uvx prusaslicer-mcp
85
+ ```
86
+
87
+ or install it with `pip install prusaslicer-mcp` (or `pipx install prusaslicer-mcp`) and run `prusaslicer-mcp`.
88
+ The server talks MCP over stdio, so you normally do not run it yourself: you register it with a client, below.
89
+
90
+ ### Configuration
91
+
92
+
93
+
94
+ | Variable | Purpose | Default |
95
+ |---|---|---|
96
+ | `PRUSASLICER_PATH` | PrusaSlicer executable | auto-detected (`PATH`, `/Applications/**/PrusaSlicer.app`, ...) |
97
+ | `PRUSASLICER_DATADIR` | Profile/config directory | platform default |
98
+ | `PRUSASLICER_MCP_OUTPUT_DIR` | Where G-code goes when no output path is given; created in full if missing | `./output` |
99
+ | `PRUSASLICER_MCP_TIMEOUT` | Slice timeout (seconds, must be positive) | `600` |
100
+
101
+ ## Register with an MCP client
102
+
103
+ **Claude Code:**
104
+
105
+ ```bash
106
+ claude mcp add prusaslicer \
107
+ -e PRUSASLICER_MCP_OUTPUT_DIR="$HOME/prusaslicer-output" \
108
+ -- uvx prusaslicer-mcp
109
+ ```
110
+
111
+ **Claude Desktop and other clients** take the same command in their configuration file:
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "prusaslicer": {
117
+ "command": "uvx",
118
+ "args": ["prusaslicer-mcp"],
119
+ "env": { "PRUSASLICER_MCP_OUTPUT_DIR": "/Users/you/prusaslicer-output" }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ Set `PRUSASLICER_MCP_OUTPUT_DIR`: the default, `./output`, is relative to wherever the client starts the server,
126
+ which is rarely a folder you want G-code in. `PRUSASLICER_PATH` is only needed when auto-detection fails (a
127
+ nonstandard install folder; quote paths containing spaces). `claude mcp list` should show `prusaslicer`.
128
+
129
+ These are ordinary environment variables, so give them through the client's `env` setting, as above. The `.env`
130
+ file in a clone is only a convenience for development.
131
+
132
+ ## Notes
133
+
134
+ - STEP files (`.step`, `.stp`) work in every tool: `inspect_model` and `preview_model` have PrusaSlicer convert them to a temporary mesh, so their triangle count and watertightness describe PrusaSlicer's tessellation ([ADR 0016](docs/decisions/0016-read-step-files-through-prusaslicer.md)).
135
+ - Transforms use PrusaSlicer's own flags: rotations apply in the fixed order X, then Y, then Z (with one oddity, exactly +-90 about Y together with a Z rotation, so check with `preview_model(rotate_*)`, which asks PrusaSlicer itself: [ADR 0015](docs/decisions/0015-preview-transforms-by-asking-prusaslicer.md)); `scale` is a factor (0.5 is half, not 50%); `copies` is the total number of objects. Plates built from several files are not supported, because `--merge` crashes PrusaSlicer 2.9.7 ([ADR 0012](docs/decisions/0012-transform-with-prusaslicers-own-flags.md)).
136
+ - A slice that succeeds is not always printable. A scan exported Y-up slices without an error and with plausible statistics, but it lies on its side, so parts of its height print nothing. `preview_model` shows that at a glance and `preview_gcode` (view `profile`) shows the empty stretches. A scan can also have a real hole, such as a missing base, which PrusaSlicer refuses (`no extrusions in the first layer`); `inspect_model` reports it and `slice_model(close_holes=true)` caps it in a temporary copy, leaving the file alone ([ADR 0014](docs/decisions/0014-cap-flat-holes-on-request.md)), with a flat base even where the scan's rim is ragged ([ADR 0017](docs/decisions/0017-close-a-hole-with-a-flat-base.md)). A scan's base is rarely square to its axis (the statue's is 16 degrees off), so stand it on the capped base rather than on an axis, as `suggest_orientation(close_holes=true)` does: a tilted base touches the bed along one edge ([ADR 0018](docs/decisions/0018-suggest-orientations-from-geometry.md)). Only simple, nearly flat holes are capped, so look at `preview_model` first: a cup scanned open at the top would be closed too. `slice_model` returns PrusaSlicer's own warnings in full and adds a warning when layers skip a large stretch of the height, so read `warnings` before trusting the numbers.
137
+ - Plain 3MF files (exported from CAD, not saved from PrusaSlicer) keep their stored coordinates, so a part modelled around the origin sits half off the bed. `slice_model` therefore centres a plain 3MF on the bed by default (`center` omitted) and reports `centered`; pass `center=false` to keep its stored position, or `center=true` to centre a project, STL or OBJ. STL and OBJ files are placed by PrusaSlicer, and projects keep their own layout ([ADR 0013](docs/decisions/0013-centre-plain-3mf-files-automatically.md)).
138
+ - When PrusaSlicer refuses a slice, the error keeps its own message and adds advice where we know the remedy: a model larger than the bed (with its size, the bed's size, `scale_to_fit` and, if it fits across the diagonal, `rotate_z=45`), an empty first layer (`preview_model`, `rotate_x`/`rotate_y`, repair), and the wipe-tower conflict (an override).
139
+
140
+ - `--query-*` actions exit with status 1 on success in 2.9.7; the server judges them by their output file.
141
+ - `slice_model`'s `output_path` may create its last folder only; if the folder above it does not exist the call is refused, because that is almost always a typo.
142
+ - Setting overrides use PrusaSlicer config option names, e.g. `{"fill_density": "25%"}` (see `PrusaSlicer --help-fff`). Options the server sets itself (profiles, output, `binary_gcode`, `datadir`, `save`) are rejected.
143
+
144
+ ## Limitations
145
+
146
+ - No live GUI control: each call runs a separate PrusaSlicer process and cannot see what is open in the GUI.
147
+ - Output is always text G-code, even where the profile prefers binary (`.bgcode`).
148
+ - FFF (filament) slicing only; single material; no transform options yet. See the [roadmap](ROADMAP.md).
149
+
150
+ ## Development
151
+
152
+ Work from a clone (see [CONTRIBUTING.md](CONTRIBUTING.md)):
153
+
154
+ ```bash
155
+ git clone https://github.com/sebastienc/prusaslicer-mcp && cd prusaslicer-mcp
156
+ uv sync
157
+ cp .env.example .env # then edit; machine-specific, git-ignored
158
+ direnv allow # optional: loads .env into your shell
159
+ claude mcp add --scope project prusaslicer -- uv run prusaslicer-mcp # run the checkout, not the PyPI release
160
+ ```
161
+
162
+ The repository ships a `.mcp.json` that registers the checkout for Claude Code (it contains no paths; `uv run`
163
+ resolves the project from the working directory). In VS Code, open `prusaslicer-mcp.code-workspace`.
164
+
165
+ Everything runs through `uv`; [Poe the Poet](https://poethepoet.natn.io/) chains the checks (uv has no task runner).
166
+
167
+ ```bash
168
+ uv sync # install
169
+ uv run poe check # lint, format check, type check, unit tests (what CI runs, minus integration)
170
+ uv run poe fix # apply safe lint fixes, then format
171
+ uv run poe test-integration # tests that need a real PrusaSlicer (skip when absent)
172
+ uv run poe # list every task
173
+ uv run poe dogfood run <folder> # run every tool over a folder of real models and report (opt-in; docs/dogfooding.md)
174
+ ```
175
+
176
+ `uv format`, `uv check` and `uv audit` are uv's own formatter, type checker (ty, pinned) and dependency audit.
177
+ Ruff does the linting and pyright type-checks alongside ty ([ADR 0010](docs/decisions/0010-pin-ty-and-add-pyright.md)). Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat: ...`, `fix: ...`):
178
+ CI checks them with commitlint, and [Release Please](https://github.com/googleapis/release-please) opens a release
179
+ pull request from them. See [ADR 0008](docs/decisions/0008-conventional-commits-and-release-please.md) and
180
+ [ADR 0009](docs/decisions/0009-quality-gate-toolchain.md).
181
+
182
+ ## Documentation
183
+
184
+ - [Roadmap](ROADMAP.md): what's planned
185
+ - [Architecture](docs/architecture/README.md): how the server works
186
+ - [Decision records](docs/decisions/README.md): why it works that way (MADR)
187
+ - [`CLAUDE.md`](CLAUDE.md) and `.claude/rules/`: conventions for Claude Code sessions in this repo
188
+
189
+ ## License
190
+
191
+ [MIT](LICENSE). PrusaSlicer itself is AGPL-3.0 and is invoked as a separate program, not bundled or linked.
192
+
193
+ See [CONTRIBUTING.md](CONTRIBUTING.md) to help, and [SECURITY.md](SECURITY.md) to report a vulnerability.
@@ -0,0 +1,163 @@
1
+ # prusaslicer-mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude (or any MCP client) drive the
4
+ **PrusaSlicer 2.9.x command line**: discover installed printers and profiles, sanity-check and look at a model,
5
+ work out which way up to print it, slice it headlessly with your real presets, and get print time and filament use back.
6
+
7
+ It runs PrusaSlicer as a separate process, so it works while the GUI is open and does not touch it.
8
+ It does **not** control the GUI itself (PrusaSlicer 2.9.x has no remote-control API), and it targets
9
+ **2.9.x only**; PrusaSlicer 3.x is not supported yet.
10
+
11
+ **Status:** early. Seven tools, tested against PrusaSlicer 2.9.7 on macOS. See the [roadmap](ROADMAP.md).
12
+
13
+ *An independent project. It is not affiliated with, endorsed by or supported by Prusa Research. PrusaSlicer and Prusa are trademarks of Prusa Research a.s.*
14
+
15
+ ## Example
16
+
17
+ Once registered with an MCP client, you can ask things like:
18
+
19
+ > "Which printers do I have installed? Check `~/models/bracket.stl` and slice it on the MINI with
20
+ > Prusament PLA at 0.20mm, 25% infill. How long will it take and how much filament does it use?"
21
+
22
+ Behind that, the client calls `list_printers` → `list_profiles` → `inspect_model` → `slice_model` and
23
+ reads back the print time and filament use. `preview_model` and `preview_gcode` let it *look* at the model and at what was sliced, and `suggest_orientation` proposes which way up to print.
24
+
25
+ A scan that will not slice is a different conversation:
26
+
27
+ > "`~/scans/statue.stl` won't slice. What's wrong with it, and which way up should I print it?"
28
+
29
+ Behind that: `inspect_model` reports an open base, `suggest_orientation` with `close_holes` finds the orientation that stands the capped base flat on the bed (and, given your profiles, measures what the supports would cost in each), `preview_model` shows it before anything is sliced, and `slice_model` prints it.
30
+
31
+ ## Tools
32
+
33
+ | Tool | What it does |
34
+ |---|---|
35
+ | `list_printers` | Installed printer models, exact printer profile names, nozzle variants, bed sizes |
36
+ | `list_profiles` | Print (quality) profiles and compatible filament profiles for a printer profile |
37
+ | `inspect_model` | Size, volume, watertightness, open holes (and how many are flat gaps `close_holes` can cap), part count; optional build-volume fit check (via `trimesh`), a warning when a 3MF's stored position is off the bed, and, for a 3MF saved from PrusaSlicer, the profiles and key settings (infill, brim, supports) stored in it |
38
+ | `slice_model` | Slice with `printer_profile` / `print_profile` / `material_profile` (all three, or none for a PrusaSlicer project, which then keeps its own saved settings), optional setting overrides, `center` (plain 3MF files are centred automatically), transforms (`rotate_x/y/z`, `scale`, `scale_to_fit`, `split`, `copies`), and `close_holes` to cap a flat hole such as a scan's missing base; returns G-code path, time, filament, cost, layers, whether the project's or the profiles' settings were used, whether the model was centred, and how many holes were closed |
39
+ | `suggest_orientation` | Ranks the ways to stand a model: its large flat faces, the axes and the orientation as stored, each with the `rotate_x`/`rotate_y` to reach it, its height, how much of it lies on the bed, how much overhang would need support, and whether it rests on a real base. `close_holes` lets a scan's missing base count. Geometry only (near-equal overhang is a tie), unless you also give the three profiles: then each option, at most six, is sliced with supports on and reports its real support filament, filament and print time (slow, one slice each) |
40
+ | `preview_model` | A PNG of a model from iso, front, side or top, or all four on one sheet, with its size and which way is up; shows a Y-up scan lying on its side at a glance. Takes the same `rotate_*`, `scale` and `scale_to_fit` as `slice_model`, and `close_holes`, and draws the model as PrusaSlicer will transform it, so an orientation can be checked before slicing. A pale grid is the bed, level with the model's lowest point, so it is plain which face rests on it |
41
+ | `preview_gcode` | A PNG of sliced G-code: one layer's toolpaths coloured by feature, or front and side silhouettes of everything printed, where an empty stretch shows as a blank band |
42
+
43
+ Profile names must match exactly; use `list_printers` and `list_profiles` to discover them.
44
+
45
+ **Projects.** A 3MF saved from PrusaSlicer carries its own settings (supports, brim, infill...). Slice it with no profiles to keep them; passing profiles makes PrusaSlicer ignore them and use the plain profile values ([ADR 0007](docs/decisions/0007-slice-prusaslicer-projects-with-their-own-settings.md)). `inspect_model` shows what a project saves.
46
+ G-code is always written as text (`--binary-gcode=0`) so statistics can be read back.
47
+
48
+ ## Install
49
+
50
+ Requires Python 3.12+ and PrusaSlicer 2.9.x. The easiest way is [uv](https://docs.astral.sh/uv/), which runs the
51
+ server without a separate install step:
52
+
53
+ ```bash
54
+ uvx prusaslicer-mcp
55
+ ```
56
+
57
+ or install it with `pip install prusaslicer-mcp` (or `pipx install prusaslicer-mcp`) and run `prusaslicer-mcp`.
58
+ The server talks MCP over stdio, so you normally do not run it yourself: you register it with a client, below.
59
+
60
+ ### Configuration
61
+
62
+
63
+
64
+ | Variable | Purpose | Default |
65
+ |---|---|---|
66
+ | `PRUSASLICER_PATH` | PrusaSlicer executable | auto-detected (`PATH`, `/Applications/**/PrusaSlicer.app`, ...) |
67
+ | `PRUSASLICER_DATADIR` | Profile/config directory | platform default |
68
+ | `PRUSASLICER_MCP_OUTPUT_DIR` | Where G-code goes when no output path is given; created in full if missing | `./output` |
69
+ | `PRUSASLICER_MCP_TIMEOUT` | Slice timeout (seconds, must be positive) | `600` |
70
+
71
+ ## Register with an MCP client
72
+
73
+ **Claude Code:**
74
+
75
+ ```bash
76
+ claude mcp add prusaslicer \
77
+ -e PRUSASLICER_MCP_OUTPUT_DIR="$HOME/prusaslicer-output" \
78
+ -- uvx prusaslicer-mcp
79
+ ```
80
+
81
+ **Claude Desktop and other clients** take the same command in their configuration file:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "prusaslicer": {
87
+ "command": "uvx",
88
+ "args": ["prusaslicer-mcp"],
89
+ "env": { "PRUSASLICER_MCP_OUTPUT_DIR": "/Users/you/prusaslicer-output" }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ Set `PRUSASLICER_MCP_OUTPUT_DIR`: the default, `./output`, is relative to wherever the client starts the server,
96
+ which is rarely a folder you want G-code in. `PRUSASLICER_PATH` is only needed when auto-detection fails (a
97
+ nonstandard install folder; quote paths containing spaces). `claude mcp list` should show `prusaslicer`.
98
+
99
+ These are ordinary environment variables, so give them through the client's `env` setting, as above. The `.env`
100
+ file in a clone is only a convenience for development.
101
+
102
+ ## Notes
103
+
104
+ - STEP files (`.step`, `.stp`) work in every tool: `inspect_model` and `preview_model` have PrusaSlicer convert them to a temporary mesh, so their triangle count and watertightness describe PrusaSlicer's tessellation ([ADR 0016](docs/decisions/0016-read-step-files-through-prusaslicer.md)).
105
+ - Transforms use PrusaSlicer's own flags: rotations apply in the fixed order X, then Y, then Z (with one oddity, exactly +-90 about Y together with a Z rotation, so check with `preview_model(rotate_*)`, which asks PrusaSlicer itself: [ADR 0015](docs/decisions/0015-preview-transforms-by-asking-prusaslicer.md)); `scale` is a factor (0.5 is half, not 50%); `copies` is the total number of objects. Plates built from several files are not supported, because `--merge` crashes PrusaSlicer 2.9.7 ([ADR 0012](docs/decisions/0012-transform-with-prusaslicers-own-flags.md)).
106
+ - A slice that succeeds is not always printable. A scan exported Y-up slices without an error and with plausible statistics, but it lies on its side, so parts of its height print nothing. `preview_model` shows that at a glance and `preview_gcode` (view `profile`) shows the empty stretches. A scan can also have a real hole, such as a missing base, which PrusaSlicer refuses (`no extrusions in the first layer`); `inspect_model` reports it and `slice_model(close_holes=true)` caps it in a temporary copy, leaving the file alone ([ADR 0014](docs/decisions/0014-cap-flat-holes-on-request.md)), with a flat base even where the scan's rim is ragged ([ADR 0017](docs/decisions/0017-close-a-hole-with-a-flat-base.md)). A scan's base is rarely square to its axis (the statue's is 16 degrees off), so stand it on the capped base rather than on an axis, as `suggest_orientation(close_holes=true)` does: a tilted base touches the bed along one edge ([ADR 0018](docs/decisions/0018-suggest-orientations-from-geometry.md)). Only simple, nearly flat holes are capped, so look at `preview_model` first: a cup scanned open at the top would be closed too. `slice_model` returns PrusaSlicer's own warnings in full and adds a warning when layers skip a large stretch of the height, so read `warnings` before trusting the numbers.
107
+ - Plain 3MF files (exported from CAD, not saved from PrusaSlicer) keep their stored coordinates, so a part modelled around the origin sits half off the bed. `slice_model` therefore centres a plain 3MF on the bed by default (`center` omitted) and reports `centered`; pass `center=false` to keep its stored position, or `center=true` to centre a project, STL or OBJ. STL and OBJ files are placed by PrusaSlicer, and projects keep their own layout ([ADR 0013](docs/decisions/0013-centre-plain-3mf-files-automatically.md)).
108
+ - When PrusaSlicer refuses a slice, the error keeps its own message and adds advice where we know the remedy: a model larger than the bed (with its size, the bed's size, `scale_to_fit` and, if it fits across the diagonal, `rotate_z=45`), an empty first layer (`preview_model`, `rotate_x`/`rotate_y`, repair), and the wipe-tower conflict (an override).
109
+
110
+ - `--query-*` actions exit with status 1 on success in 2.9.7; the server judges them by their output file.
111
+ - `slice_model`'s `output_path` may create its last folder only; if the folder above it does not exist the call is refused, because that is almost always a typo.
112
+ - Setting overrides use PrusaSlicer config option names, e.g. `{"fill_density": "25%"}` (see `PrusaSlicer --help-fff`). Options the server sets itself (profiles, output, `binary_gcode`, `datadir`, `save`) are rejected.
113
+
114
+ ## Limitations
115
+
116
+ - No live GUI control: each call runs a separate PrusaSlicer process and cannot see what is open in the GUI.
117
+ - Output is always text G-code, even where the profile prefers binary (`.bgcode`).
118
+ - FFF (filament) slicing only; single material; no transform options yet. See the [roadmap](ROADMAP.md).
119
+
120
+ ## Development
121
+
122
+ Work from a clone (see [CONTRIBUTING.md](CONTRIBUTING.md)):
123
+
124
+ ```bash
125
+ git clone https://github.com/sebastienc/prusaslicer-mcp && cd prusaslicer-mcp
126
+ uv sync
127
+ cp .env.example .env # then edit; machine-specific, git-ignored
128
+ direnv allow # optional: loads .env into your shell
129
+ claude mcp add --scope project prusaslicer -- uv run prusaslicer-mcp # run the checkout, not the PyPI release
130
+ ```
131
+
132
+ The repository ships a `.mcp.json` that registers the checkout for Claude Code (it contains no paths; `uv run`
133
+ resolves the project from the working directory). In VS Code, open `prusaslicer-mcp.code-workspace`.
134
+
135
+ Everything runs through `uv`; [Poe the Poet](https://poethepoet.natn.io/) chains the checks (uv has no task runner).
136
+
137
+ ```bash
138
+ uv sync # install
139
+ uv run poe check # lint, format check, type check, unit tests (what CI runs, minus integration)
140
+ uv run poe fix # apply safe lint fixes, then format
141
+ uv run poe test-integration # tests that need a real PrusaSlicer (skip when absent)
142
+ uv run poe # list every task
143
+ uv run poe dogfood run <folder> # run every tool over a folder of real models and report (opt-in; docs/dogfooding.md)
144
+ ```
145
+
146
+ `uv format`, `uv check` and `uv audit` are uv's own formatter, type checker (ty, pinned) and dependency audit.
147
+ Ruff does the linting and pyright type-checks alongside ty ([ADR 0010](docs/decisions/0010-pin-ty-and-add-pyright.md)). Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat: ...`, `fix: ...`):
148
+ CI checks them with commitlint, and [Release Please](https://github.com/googleapis/release-please) opens a release
149
+ pull request from them. See [ADR 0008](docs/decisions/0008-conventional-commits-and-release-please.md) and
150
+ [ADR 0009](docs/decisions/0009-quality-gate-toolchain.md).
151
+
152
+ ## Documentation
153
+
154
+ - [Roadmap](ROADMAP.md): what's planned
155
+ - [Architecture](docs/architecture/README.md): how the server works
156
+ - [Decision records](docs/decisions/README.md): why it works that way (MADR)
157
+ - [`CLAUDE.md`](CLAUDE.md) and `.claude/rules/`: conventions for Claude Code sessions in this repo
158
+
159
+ ## License
160
+
161
+ [MIT](LICENSE). PrusaSlicer itself is AGPL-3.0 and is invoked as a separate program, not bundled or linked.
162
+
163
+ See [CONTRIBUTING.md](CONTRIBUTING.md) to help, and [SECURITY.md](SECURITY.md) to report a vulnerability.
@@ -0,0 +1,191 @@
1
+ [project]
2
+ name = "prusaslicer-mcp"
3
+ version = "0.3.1"
4
+ description = "MCP server that lets Claude drive the PrusaSlicer 2.9.x CLI"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ keywords = [
9
+ "mcp",
10
+ "model-context-protocol",
11
+ "prusaslicer",
12
+ "3d-printing",
13
+ "slicer",
14
+ "gcode",
15
+ "claude",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: End Users/Desktop",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Multimedia :: Graphics :: 3D Modeling",
26
+ ]
27
+ requires-python = ">=3.12"
28
+ dependencies = [
29
+ "lxml>=6.1.3",
30
+ "mcp>=2.1.0",
31
+ "networkx>=3.7",
32
+ "numpy>=2.5.4",
33
+ "pillow>=13.0.0",
34
+ "pydantic>=2.14.3",
35
+ "trimesh>=5.2.1",
36
+ ]
37
+
38
+ [[project.authors]]
39
+ name = "Sebastien Coutu"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/sebastienc/prusaslicer-mcp"
43
+ Repository = "https://github.com/sebastienc/prusaslicer-mcp"
44
+ Issues = "https://github.com/sebastienc/prusaslicer-mcp/issues"
45
+ Changelog = "https://github.com/sebastienc/prusaslicer-mcp/blob/main/CHANGELOG.md"
46
+
47
+ [project.scripts]
48
+ prusaslicer-mcp = "prusaslicer_mcp:main"
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.12.9,<0.13.0"]
52
+ build-backend = "uv_build"
53
+
54
+ [dependency-groups]
55
+ dev = [
56
+ "poethepoet>=0.49.0",
57
+ "pyright>=1.1.414",
58
+ "pytest>=9.2.0",
59
+ "pytest-asyncio>=1.5.0",
60
+ "pytest-cov>=7.1.0",
61
+ "ruff>=0.15,<0.16",
62
+ ]
63
+
64
+ [tool.pytest.ini_options]
65
+ asyncio_mode = "auto"
66
+ asyncio_default_fixture_loop_scope = "function"
67
+ testpaths = ["tests"]
68
+ pythonpath = ["scripts"]
69
+ addopts = "--strict-markers"
70
+ markers = ["integration: needs a real PrusaSlicer install; added automatically to any test that uses the `prusaslicer` fixture"]
71
+
72
+ [tool.coverage.run]
73
+ source = ["prusaslicer_mcp"]
74
+ branch = true
75
+
76
+ [tool.coverage.report]
77
+ show_missing = true
78
+ fail_under = 95
79
+
80
+ [tool.ruff]
81
+ line-length = 120
82
+ target-version = "py312"
83
+
84
+ [tool.ruff.lint]
85
+ select = [
86
+ "E",
87
+ "F",
88
+ "W",
89
+ "I",
90
+ "UP",
91
+ "B",
92
+ "SIM",
93
+ "PT",
94
+ "ASYNC",
95
+ "RUF",
96
+ "C4",
97
+ "PIE",
98
+ "RET",
99
+ ]
100
+ ignore = [
101
+ "E501",
102
+ "ASYNC109",
103
+ "ASYNC240",
104
+ ]
105
+
106
+ [tool.pyright]
107
+ include = [
108
+ "src",
109
+ "tests",
110
+ "scripts",
111
+ ]
112
+ pythonVersion = "3.12"
113
+ typeCheckingMode = "basic"
114
+ extraPaths = ["scripts"]
115
+
116
+ [tool.ty.environment]
117
+ extra-paths = ["scripts"]
118
+
119
+ [tool.poe.tasks.lint]
120
+ cmd = "ruff check"
121
+ help = "Lint with Ruff"
122
+
123
+ [tool.poe.tasks.lint-fix]
124
+ cmd = "ruff check --fix"
125
+ help = "Lint and apply safe fixes"
126
+
127
+ [tool.poe.tasks.format]
128
+ cmd = "uv format"
129
+ help = "Format with Ruff (via uv format)"
130
+
131
+ [tool.poe.tasks.format-check]
132
+ cmd = "uv format --check"
133
+ help = "Fail if any file needs formatting"
134
+
135
+ [tool.poe.tasks.typecheck-ty]
136
+ cmd = "uv check --ty-version 0.0.104"
137
+ help = "Type-check with ty (uv check), pinned because ty is pre-1.0"
138
+
139
+ [tool.poe.tasks.typecheck-pyright]
140
+ cmd = "pyright"
141
+ help = "Type-check with pyright"
142
+
143
+ [tool.poe.tasks.typecheck]
144
+ sequence = [
145
+ "typecheck-ty",
146
+ "typecheck-pyright",
147
+ ]
148
+ help = "Type-check with both checkers"
149
+
150
+ [tool.poe.tasks.audit]
151
+ cmd = "uv audit"
152
+ help = "Check locked dependencies for known vulnerabilities"
153
+
154
+ [tool.poe.tasks.test]
155
+ cmd = "pytest -m 'not integration'"
156
+ help = "Unit tests; no PrusaSlicer needed"
157
+
158
+ [tool.poe.tasks.test-integration]
159
+ cmd = "pytest -m integration"
160
+ help = "Integration tests; need a real PrusaSlicer"
161
+
162
+ [tool.poe.tasks.test-all]
163
+ cmd = "pytest"
164
+ help = "Every test (integration tests skip when PrusaSlicer is absent)"
165
+
166
+ [tool.poe.tasks.cov]
167
+ cmd = "pytest -m 'not integration' --cov"
168
+ help = "Unit tests with a coverage report"
169
+
170
+ [tool.poe.tasks.check]
171
+ sequence = [
172
+ "lint",
173
+ "format-check",
174
+ "typecheck",
175
+ "test",
176
+ ]
177
+ help = "Everything CI checks, apart from integration tests"
178
+
179
+ [tool.poe.tasks.fix]
180
+ sequence = [
181
+ "lint-fix",
182
+ "format",
183
+ ]
184
+ help = "Apply safe lint fixes, then format"
185
+
186
+ [tool.poe.tasks.dogfood]
187
+ cmd = "python -m dogfood.sweep"
188
+ help = "Run every tool over a folder of models: `poe dogfood run <folder>` (docs/dogfooding.md)"
189
+
190
+ [tool.poe.tasks.dogfood.env]
191
+ PYTHONPATH = "scripts"