Skip to main content
Glama
vibeDN
by vibeDN
README.md
# NeuroPaint-X

An MCP server that gives an AI an **Ibis-Paint-level image editor** — a layered
raster **painting canvas** it drives with tool calls, renders, *looks at*, and
iterates on. (Python package name: `mcp_paint`.)

- **Stateful documents** held in server memory: layers, blend modes, opacity,
  alpha-lock, undo/redo.
- **skia-python** rendering: anti-aliased brushes (soft/hard/ink/marker/airbrush/
  pencil), shapes, linear/radial gradients, flood fill, text, filters
  (blur / drop-shadow / glow / grayscale / saturate / brightness / contrast /
  invert / sharpen).
- **Pulls images and fonts straight from the internet** — `place_image(url=…)`,
  `draw_text(google_font="Poppins")` or `font_url=…`.
- **Token-frugal by design** (see below).

## Keeping token cost low

Image tokens ≈ `width*height / 750`. NeuroPaint-X minimises them:

| lever | effect |
|---|---|
| Mutating tools return **one terse text line**, never an image | you only pay image tokens when you *choose* to look |
| `render(detail=…)` downscales | `thumb` ≈ 256px (~60 tok), `preview` ≈ 640px (~370 tok), `full` ≈ 1024px, `max` ≈ 1600px |
| `render(region=[x,y,w,h])` | inspect a detail at high zoom without paying for the whole canvas |
| `batch(ops_list=[…])` | dozens of draw ops in **one** tool call = one round-trip, one undo step |
| `export(...)` | writes a full-res PNG to disk — **zero** image tokens |

Each `render` reply also reports `approx_image_tokens` so the model can self-budget.

## Run it

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e .

# HTTP (for a Claude connector) — default 127.0.0.1:8765
python -m mcp_paint

# or stdio (for local Claude Code / Desktop)
MCP_PAINT_TRANSPORT=stdio python -m mcp_paint
```

### Environment

| var | default | meaning |
|---|---|---|
| `MCP_PAINT_TRANSPORT` | `http` | `http` or `stdio` |
| `MCP_PAINT_HOST` / `MCP_PAINT_PORT` | `127.0.0.1` / `8765` | HTTP bind |
| `MCP_PAINT_TOKEN` | *(unset)* | if set, require `Authorization: Bearer <token>` |
| `MCP_PAINT_EXPORT` | `~/mcp-paint-output` | where `export` writes files |
| `MCP_PAINT_CACHE` | `~/.cache/mcp-paint` | downloaded images/fonts |
| `MCP_PAINT_MAX_DOWNLOAD` | `25165824` | max bytes per fetched asset |

## Use as a Claude connector (self-hosted)

1. Run it on your box, bound to localhost, **with a token**:
   ```bash
   MCP_PAINT_TOKEN=$(openssl rand -hex 24) python -m mcp_paint
   ```
2. Expose that port over HTTPS with a tunnel:
   ```bash
   cloudflared tunnel --url http://localhost:8765        # quick throwaway URL
   # or: tailscale funnel 8765   /   ngrok http 8765
   ```
3. In Claude → **Settings → Connectors → Add custom connector**:
   - URL `https://<your-tunnel>/mcp`
   - **Authentication: None** (the server uses a shared header secret, not OAuth)
   - **Additional request headers → Add header:**
     `X-Paint-Token` = `<your MCP_PAINT_TOKEN>`
     (`Authorization` = `Bearer <token>` also works)

> With no `MCP_PAINT_TOKEN` the server is open to anyone who has the tunnel URL.
> Set one.

## Local Claude Code

`.mcp.json` (see `.mcp.json.example`):

```json
{
  "mcpServers": {
    "paint": {
      "command": "/ABS/PATH/NeuroPaint-X/.venv/bin/python",
      "args": ["-m", "mcp_paint"],
      "env": { "MCP_PAINT_TRANSPORT": "stdio" }
    }
  }
}
```

## Tool map

`new_canvas` · `list_canvases` · `canvas_info` · `delete_canvas`
`render` · `export` · `pick_color` · `undo` · `redo`
`add_layer` · `select_layer` · `update_layer` · `delete_layer` · `duplicate_layer`
· `move_layer` · `merge_down` · `clear_layer`
`stroke` · `draw_rect` · `draw_ellipse` · `draw_line` · `draw_polygon`
· `draw_gradient` · `bucket_fill` · `draw_text` · `place_image` · `filter_layer`
`batch`

Coordinates are pixels, origin top-left. Colors: `#rgb` / `#rrggbb` /
`#rrggbbaa` / `rgb()` / `rgba()` / CSS names / `[r,g,b(,a)]`.