Skip to main content
Glama
3D Print Recovery Assistant MCP Server

3D Print Recovery Assistant MCP Server

README.md
# 3D Print Recovery Assistant: MCP Server

CMU 24-679 (Automated Science), Homework 5, Question 4.

This is a local [Model Context Protocol](https://modelcontextprotocol.io) server. It exposes five read-only tools for deciding what to do about a failing or failed FDM 3D print. Tools 1–3 come unchanged from Homework 4. Tools 4–5 are new.

| # | Tool | Question it answers | Origin |
|---|---|---|---|
| 1 | `check_functional_geometry` | Does the container still hold the required volume? | HW4 |
| 2 | `check_recovery_feasibility` | Can the remaining layers absorb the contour correction? | HW4 |
| 3 | `expected_recovery_savings` | Does trying to recover save time and filament on average? | HW4 |
| 4 | `early_stop_trend_alarm` | While printing: should I stop now (OK / WATCH / STOP), and what would stopping save? | **new** |
| 5 | `layer_shift_strength_check` | After a layer shift: does the part still carry its load? | **new** |

The server runs locally over stdio and makes no network calls. It never sends commands to a printer.

## Setup

You need Python 3.10 or later. Node 22 or later is only needed for MCP Inspector.

```bash
git clone https://github.com/ishaanamahajan/print-recovery-mcp.git
cd print-recovery-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m pytest -q          # 20 tests, including hand-calculation checks
```

## Connect a client

**MCP Inspector (GUI)**

```bash
npx @modelcontextprotocol/inspector .venv/bin/python server.py
```

**MCP Inspector (CLI, reproduces the demo)**

```bash
bash demo/run_inspector_demo.sh        # writes demo/inspector_transcript.md
```

**Claude Code**

```bash
claude mcp add print-recovery -- "$PWD/.venv/bin/python" "$PWD/server.py"
```

**Claude Desktop.** Add this to `claude_desktop_config.json`, using absolute paths:

```json
{ "mcpServers": { "print-recovery": {
    "command": "/ABS/PATH/print-recovery-mcp/.venv/bin/python",
    "args": ["/ABS/PATH/print-recovery-mcp/server.py"] } } }
```

> **Apple Silicon with an x86_64 (Rosetta) Node.** Clients built on Node launch Python under the same architecture as Node. The arm64 `pydantic_core` then fails to load and the client reports "Connection closed". To fix it, launch with `arch -arm64 /path/.venv/bin/python server.py` (the demo script does this automatically) or install an arm64 Node.

## Tool contracts

All five tools follow the same rules.

- **Inputs.** Each tool has a JSON Schema in which every number carries a `minimum` and `maximum` and a unit in both its name and its description. The client sees these ranges before it calls.
- **Results.** Each tool has an `outputSchema`. A call returns `structuredContent` (typed JSON) plus the same JSON as text.
- **Annotations.** Every tool is marked `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true` and `openWorldHint: false`.
- **Invalid inputs.** No partial result is ever returned. The server rejects bad input in one of two layers, and both return `isError: true` with a message naming the field:
  1. **Schema layer (pydantic).** Wrong types, out-of-range values and lists that are too short are rejected before the tool runs. Example: `success_probability = 80` gives "Input should be less than or equal to 1".
  2. **Domain layer (the tool's own checks).** These catch rules that involve more than one input, such as wall thickness ≥ radius, mismatched list lengths, layers not increasing, the last measured layer past the end of the print, or no load given. They are raised as `ToolError("Invalid input: …")`.

### 1. `check_functional_geometry` (HW4)

$V = \pi (r-t)^2 (H-b) / 1000$ mL

| Input | Unit | Range |
|---|---|---|
| `outer_radius_mm` | mm | 1–500 |
| `wall_thickness_mm` | mm | 0.4–50, < radius |
| `height_mm` | mm | 1–1000 |
| `base_thickness_mm` | mm | 0.2–100, < height |
| `min_capacity_mL` | mL | 0–10⁶ |

- **Result:** `estimated_capacity_mL`, `margin_mL`, `meets_requirement`.
- **Limitation:** assumes an ideal cylinder filled to the brim. Capacity does not prove the container is watertight.

### 2. `check_recovery_feasibility` (HW4)

$N_{req} = \lceil c/\Delta_{max} \rceil$, and recovery is feasible if $N_{req} \le N_{rem}$.

| Input | Unit | Range |
|---|---|---|
| `correction_mm` | mm | 0–50 |
| `max_correction_per_layer_mm` | mm/layer | 0.01–2 |
| `remaining_layers` | layers | integer, 0–100,000 |
| `layer_height_mm` | mm | 0.04–1.0 |

- **Result:** `required_layers`, `transition_height_mm`, `spare_layers`, `feasible`.
- **Limitation:** $\Delta_{max}$ is an assumed value, not a measured capability of the printer.

### 3. `expected_recovery_savings` (HW4)

$E[T] = T_{int} + T_{rec} + (1-p)T_{restart}$ and $E[F] = F_{rec} + (1-p)F_{restart}$.

| Input | Unit | Range |
|---|---|---|
| `restart_time_h` | h | 0.01–1000 |
| `recovery_time_h` | h | 0–1000 |
| `intervention_time_h` | h | 0–100 |
| `restart_filament_g` | g | 0.1–10⁵ |
| `recovery_filament_g` | g | 0–10⁵ |
| `success_probability` | fraction | 0–1 |

- **Result:** `expected_recovery_time_h`, `expected_time_savings_h`, `expected_recovery_filament_g`, `expected_filament_savings_g`, `breakeven_probability_time`.
- **Limitation:** risk-neutral and assumes a single recovery attempt. The user supplies $p$.

### 4. `early_stop_trend_alarm` (new)

The tool fits a least-squares line $d(n) = a + bn$ to deviations measured during the print, such as warp lift, layer-shift offset or under-extrusion width, and extrapolates it to the final layer.

| Alert | Condition |
|---|---|
| **STOP** | Latest deviation ≥ tolerance, or the projected final deviation ≥ tolerance |
| **WATCH** | Either value ≥ `watch_fraction` × tolerance |
| **OK** | Otherwise |

| Input | Unit | Range |
|---|---|---|
| `layer_numbers` | layers | list of 3–10,000 integers, strictly increasing, 0–100,000 |
| `deviations_mm` | mm | list of the same length, each 0–50 |
| `tolerance_mm` | mm | 0.01–50 |
| `total_layers` | layers | integer 1–100,000, ≥ last measured layer |
| `watch_fraction` | fraction | 0.1–0.99 (default 0.5) |
| `total_print_time_h` | h | 0.01–1000 (optional) |
| `total_filament_g` | g | 0.1–10⁵ (optional) |

**Result:**
- `alert_level`
- `trend_slope_mm_per_layer`, `trend_r_squared`
- `current_layer`, `current_deviation_mm`
- `projected_final_deviation_mm`, `projected_exceedance_layer`, `layers_until_exceedance` (the last two are `null` if the trend never reaches the tolerance)
- `remaining_fraction_of_print`
- `time_saved_if_stopped_now_h`, `filament_saved_if_stopped_now_g` (`null` if the optional inputs are omitted)

**Limitations:**
- The trend is a straight-line extrapolation, so a low `trend_r_squared` means the projection is unreliable.
- Savings assume time and filament are spread evenly across layers.
- The deviations come from whatever is measuring the print (a camera or the user), and the tool does not check them.

### 5. `layer_shift_strength_check` (new)

A shift $s$ along the section width $W$ leaves a bonded overlap of $(W-s) \times D$. The load still acts through the centre of the upper part, so the shift adds an eccentric moment $Fs/2$.

$\sigma_{peak} = \dfrac{F}{A} + \dfrac{(M + Fs/2)\,c}{I}$, with $A = (W-s)D$, $I = D(W-s)^3/12$ and $c = (W-s)/2$.

Allowable stress = `tensile_strength_MPa` × `interlayer_strength_factor`. The result is compared against the same section with no shift.

| Input | Unit | Range |
|---|---|---|
| `section_width_mm` (along the shift) | mm | 1–500 |
| `section_depth_mm` | mm | 1–500 |
| `shift_mm` | mm | 0–500 |
| `axial_force_N` (tension across layers) | N | 0–10⁵ |
| `bending_moment_Nmm` | N·mm | 0–10⁸ |
| `tensile_strength_MPa` (datasheet) | MPa | 1–200 |
| `interlayer_strength_factor` | fraction | 0.05–1 |
| `required_safety_factor` | – | 1–20 (default 2) |

At least one of the force and the moment must be positive. A shift of at least the full width returns `safety_factor: 0` with a "separated" note rather than an error.

- **Result:** `bonded_area_mm2`, `area_retained_fraction`, `peak_stress_MPa`, `allowable_stress_MPa`, `safety_factor`, `intact_safety_factor`, `strength_retained_fraction`, `meets_requirement`, `note`.
- **Limitations:**
  - Uses beam theory on a solid rectangle. It ignores infill and perimeters, stress concentration at the step, and fatigue.
  - The interlayer factor is a user assumption and should be measured.

## Demo record

- [`demo/inspector_transcript.md`](demo/inspector_transcript.md): MCP Inspector (CLI mode) calls with full responses:
  - the tool list with schemas;
  - two HW4 tools;
  - both new tools;
  - three invalid inputs, covering a schema range, a domain rule and a new tool.
- [`demo/claude_code_transcript.md`](demo/claude_code_transcript.md): Claude Code calling the tools from plain-language questions, including a unit conversion (480 min → 8 h).

## Client / server boundary

**Server (this repo):**
- the equations;
- unit and range validation, and the input schemas that publish it;
- typed structured results;
- error messages.

It is deterministic and stateless, makes no network calls, and cannot send commands to a printer.

**Client (Claude Code, Claude Desktop, Inspector):**
- understanding the request;
- converting units (minutes → hours, percent → fraction, diameter → radius);
- choosing the tool;
- asking for missing inputs instead of inventing them;
- explaining the result.

**Human:** makes the decision. The server can report STOP, but it never stops a print.

## Link to user research (HW5 requirements)

| Requirement | Interview evidence | Tool |
|---|---|---|
| R1: Detect failures early | Unattended overnight prints; adhesion and warping failures that spaghetti detection misses; wanting to pause prints in advance | `early_stop_trend_alarm` |
| R2: Judge function, not appearance | Visually imperfect parts are acceptable if they work; a part that looks fine may still be structurally weak | `layer_shift_strength_check`, `check_functional_geometry` |
| R3: Evidence and approval before changes | Distrust of automatic correction | All tools are read-only and return intermediate values |
| R5: Run offline | Concern about proprietary designs in the cloud | Local stdio server, no network |

## What a human should verify before acting

- **All tools:** check that the arguments the client sent match the real part and printer. Units are the most likely source of error.
- **`early_stop_trend_alarm`:**
  - look at the print before cancelling;
  - the trend is a straight-line extrapolation, and a low `trend_r_squared` means it is unreliable;
  - the input measurements are not verified by the tool.
- **`layer_shift_strength_check`:**
  - use measured strength values;
  - halving `interlayer_strength_factor` from 0.5 to 0.25 drops the demo safety factor from 4.57 to about 2.3;
  - the model ignores infill, stress concentration at the step and fatigue;
  - for load-critical parts, pull-test a coupon with the same shift.
- **`check_recovery_feasibility` / `expected_recovery_savings`:** the per-layer correction limit and the success probability are assumptions that should be measured.

## Repository layout

```
tools.py       pure-Python tool functions (HW4 tools verbatim + 2 new); no MCP dependency
server.py      MCP server: schemas, result models, annotations, error mapping
tests/         pytest: hand-checked values, edge cases, invalid inputs, in-process MCP calls
demo/          Inspector CLI script + transcript, Claude Code transcript
LICENSE        MIT
```

## AI usage

Claude Code (Claude Opus 5.5) helped implement and test this server. The tool equations, ranges and design decisions were reviewed by the author.