Metadata-Version: 2.4
Name: gimp-mcp-plugin
Version: 0.3.0
Summary: GIMP MCP integration for external control of GIMP 3.2
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/gimp-mcp/gimp-mcp
Project-URL: Issues, https://github.com/gimp-mcp/gimp-mcp/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<2,>=1.28.1
Dynamic: license-file

# GIMP MCP

[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Works with Claude Desktop](https://img.shields.io/badge/Works%20with-Claude%20Desktop-7B2CBF.svg)](https://claude.ai/desktop)
[![GIMP 3.2](https://img.shields.io/badge/GIMP-3.2-orange.svg)](https://gimp.org)
[![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io)
[![CodeRabbit](https://img.shields.io/badge/CodeRabbit-AI%20Review-171717?logo=coderabbit)](https://coderabbit.ai)

## Demo

![GIMP MCP in action — AI agent driving GIMP through natural language](docs/mcpInAction.gif)

Full demo (with audio): https://github.com/gimp-mcp/gimp-mcp/raw/main/docs/demo.mp4

*AI agent using GIMP MCP to remove a background, edit a character's expression, and verify results — all through natural language via Claude*

---

## Overview

GIMP MCP bridges GIMP's professional image editing capabilities with AI assistants through the [Model Context Protocol](https://modelcontextprotocol.io). It lets you edit images by describing what you want — and gives the AI a live visual feedback channel to verify each change before moving on.

**What makes it different from other GIMP integrations:**

- The AI can *see* the image at any point in the workflow without saving to disk (`get_state_snapshot`)
- Supports fully autonomous multi-step pipelines: open → edit → verify → refine → export
- 79 dedicated tool commands covering every major GIMP operation
- Fully compatible with GIMP 3.2.x (all breaking API changes resolved)

## Key Features

| | |
|---|---|
| 👁️ **Live Visual Feedback** | `get_state_snapshot` returns a PNG preview mid-workflow so the AI verifies each step |
| 🎨 **79 GIMP Tools** | Adjustments, transforms, selections, layers, drawing, text, filters — all via MCP |
| 🔧 **GIMP 3.2 Compatible** | All GIMP 3.2 API breaks fixed and tested (59/59 passing) |
| 🔁 **Iterative Workflows** | AI loops until a goal is met — e.g. keeps removing BG until no pixels remain |
| 🖼️ **Region Snapshots** | Zoom into any area for detail verification (face, mouth, corner, etc.) |
| 🔌 **Universal MCP** | Works with Claude Desktop, Claude Code, Gemini CLI, PydanticAI, and more |

## What Can It Do?

### Background Removal with Iterative Verification
The AI removes the background, takes a snapshot to inspect the result, detects remaining pixels, and loops until the image is clean:

```
"Remove the background from this image and keep looping until only the character remains"
```

### Expression Editing
```
"Make the character smile — paint a smile arc with teeth over her mouth"
```

### Complex Multi-Step Pipelines
```
"Open navi_portrait.png, remove the background, verify it's clean,
 then make her smile and export the final result as a PNG"
```

### Color & Tone Work
```
"Boost the contrast, shift the hue 15 degrees warmer, then show me a before/after zoom of the face"
```

### Text & Compositing
```
"Add a bold title at the top in white with a subtle drop shadow, then export for web"
```

---

## Prerequisites

- **GIMP 3.2+** — tested on GIMP 3.2.2 (Windows, macOS, Linux)
- **uv** — Python package manager ([install uv](https://docs.astral.sh/uv/getting-started/installation/)); `uvx` downloads the MCP server and a suitable Python (3.11+) on first use
- **MCP-compatible AI client** — Claude Desktop, Claude Code, Gemini CLI, PydanticAI, etc.

---

## Quick Start

The MCP server and the GIMP plugin ship together as the [`gimp-mcp-plugin`](https://pypi.org/project/gimp-mcp-plugin/) package on PyPI. No clone needed.

### 1. Install the GIMP Plugin

Launch GIMP once (so its config folder exists), close it, then run:

```bash
uvx gimp-mcp-plugin@latest install-plugin
```

This copies the plugin into the newest GIMP 3.x config folder it finds (Linux standard, Snap and Flatpak locations, macOS, Windows) and makes it executable. Restart GIMP afterward.

- `--dry-run` shows where it would install without writing anything.
- `--gimp-dir DIR` installs into a specific GIMP config folder, e.g. `--gimp-dir ~/.config/GIMP/3.2`.

If you have more than one GIMP 3 installation (e.g. a system GIMP and a Flatpak), the newest folder found may not be the one your GIMP uses. Check the folder shown in GIMP's **Edit → Preferences → Folders → Plug-ins** and pass the folder that contains `plug-ins` with `--gimp-dir`.

> **Which folder does GIMP use?** GIMP names its per-user folder after its **major.minor**
> version (`3.0`, `3.2`, `3.4`, …) and creates a fresh one on each minor upgrade, so the
> plug-ins folder *moves* when you upgrade GIMP. Re-run `install-plugin` after upgrading.
> To check the active path, open **Edit → Preferences → Folders → Plug-ins** in GIMP.

### 2. Start the MCP Server in GIMP

1. Open any image in GIMP
2. Go to **Tools > MCP > Start MCP Server**
3. Server starts on `localhost:9877`

#### Start automatically when GIMP launches

To skip step 2 on every launch, open **Tools > MCP > MCP Server Settings...**, tick
**Start MCP server when GIMP launches**, and restart GIMP. Autostart is off by default.

The same dialog sets the **port** (default `9877`). If you change it, set `GIMP_MCP_PORT`
in your MCP client config so the MCP server connects to the same port:

```json
"gimp": {
  "command": "uvx",
  "args": ["gimp-mcp-plugin@latest"],
  "env": { "GIMP_MCP_PORT": "9988" }
}
```

Settings are saved to `gimp-mcp.json` in GIMP's per-user config folder (e.g.
`~/.config/GIMP/3.2/gimp-mcp.json`). Port changes apply the next time the server starts.

#### Timeout for long commands

The MCP server waits up to 300 seconds for GIMP to finish each command. For heavier work
(large or 16-bit images, slow filters), set `GIMP_MCP_TIMEOUT` (in seconds) in your MCP
client config, the same way as `GIMP_MCP_PORT` above, e.g. `"GIMP_MCP_TIMEOUT": "900"`.
`check_server` reports the timeout in effect. When a command times out, GIMP may still be
running it, so check the image before retrying. Your MCP client may have its own tool
timeout (in Claude Code, `MCP_TOOL_TIMEOUT`, in milliseconds); raise it too if it's shorter.

### 3. Configure Your MCP Client

`gimp-mcp-plugin@latest` makes `uvx` check PyPI for a new release each time your client starts the server.

#### Claude Desktop
`~/.config/Claude/claude_desktop_config.json` (Linux/macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "gimp": {
      "command": "uvx",
      "args": ["gimp-mcp-plugin@latest"]
    }
  }
}
```

#### Claude Code
```bash
claude mcp add gimp-mcp -- uvx gimp-mcp-plugin@latest
```

#### Gemini CLI
`~/.config/gemini/.gemini_config.json`:
```json
{
  "mcpServers": {
    "gimp": {
      "command": "uvx",
      "args": ["gimp-mcp-plugin@latest"]
    }
  }
}
```

#### PydanticAI
```python
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio

server = MCPServerStdio('uvx', args=['gimp-mcp-plugin@latest'])
agent = Agent('openai:gpt-4o', mcp_servers=[server])
```

#### Cancelling long commands

GIMP runs one command at a time, in the order they're sent. While it works, the MCP server
stays responsive, so your client can cancel a call. The cancel returns at once: a command
that's still waiting its turn never runs, but one GIMP has already started is finished in
the background. Check the image before retrying a cancelled edit, or it may be applied twice.

### Updating

The plugin and the MCP server must be the same version. `check_server` and `get_gimp_info` report a mismatch and print the command that fixes it. To update both:

1. Restart your MCP client. With `@latest`, it picks up the newest server.
2. Run `uvx gimp-mcp-plugin@latest install-plugin` and restart GIMP.

### Running from a Git Checkout (contributors)

To work on the code, run the server and install the plugin from a clone instead:

```bash
git clone https://github.com/gimp-mcp/gimp-mcp.git
cd gimp-mcp
uv sync
uv run gimp_mcp_server.py install-plugin    # installs this checkout's plugin
```

Then point your MCP client at the checkout, e.g. for Claude Desktop:

```json
"gimp": {
  "command": "uv",
  "args": ["run", "--directory", "/full/path/to/gimp-mcp", "gimp_mcp_server.py"]
}
```

Claude Code picks up the repo's `.mcp.json` automatically when started in the checkout. After `git pull`, re-run `install-plugin` if the plugin changed.

---

## Available MCP Tools

### 👁️ Visual Feedback

#### `get_state_snapshot(image_index, max_size, region, label)`
Returns a live PNG of the current image state — the AI's primary feedback mechanism. Call this between any edits to verify the result without saving to disk.

```python
# Full image snapshot
snapshot = get_state_snapshot(max_size=512)

# Zoom into a face region for detail inspection
snapshot = get_state_snapshot(
    region={"x": 140, "y": 80, "width": 240, "height": 300},
    max_size=512,
    label="face-check"
)
```

This enables iterative agentic workflows: **edit → snapshot → assess → refine → repeat**.

#### `get_image_bitmap(image_index, max_width, max_height, region)`
Lower-level bitmap fetch with region extraction and scaling. Returns base64-encoded PNG.

### 🎨 Adjustments
| Tool | Description |
|---|---|
| `adjust_brightness_contrast` | Brightness and contrast |
| `adjust_curves` | Curves by channel (RGB/R/G/B/A) |
| `adjust_hue_saturation` | Hue, saturation, lightness |
| `adjust_color_balance` | Shadows/midtones/highlights color balance |
| `auto_levels` | Auto-stretch levels |
| `desaturate` | Convert to grayscale (keep RGB mode) |
| `invert_colors` | Invert all channels |
| `convert_color_mode` | Convert between RGB, grayscale, and indexed modes |
| `sharpen` | Unsharp mask sharpening |
| `blur` | Gaussian blur |
| `denoise` | Noise reduction |

### 🔄 Transforms
| Tool | Description |
|---|---|
| `scale_image` | Scale to exact dimensions |
| `scale_to_fit` | Scale within bounding box (aspect-safe) |
| `crop_to_rect` | Crop to rectangle |
| `crop_to_selection` | Crop canvas to the current selection bounds |
| `rotate_image` | Rotate 90/180/270 or arbitrary angle |
| `flip_image` | Flip horizontal or vertical |
| `resize_canvas` | Resize canvas without scaling content |
| `warp_region` | Warp / liquify a region by pushing pixels in a direction |

### ✂️ Selections
| Tool | Description |
|---|---|
| `select_rectangle` | Rectangular marquee |
| `select_ellipse` | Elliptical marquee |
| `select_by_color` | Select by color (global) |
| `select_all` / `select_none` | Select all / deselect |
| `invert_selection` | Invert selection |
| `modify_selection` | Grow, shrink, feather, or border |

### 🗂️ Layers
| Tool | Description |
|---|---|
| `create_layer` | New empty layer |
| `duplicate_layer` | Duplicate active layer |
| `delete_layer` | Delete named layer |
| `rename_layer` | Rename layer |
| `set_layer_properties` | Opacity, blend mode, visibility |
| `reorder_layer` | Move layer in stack |
| `merge_visible_layers` | Flatten visible to one layer |
| `flatten_image` | Flatten all layers |
| `list_layers` | List all layers with properties |

### 🖌️ Drawing & Fill
| Tool | Description |
|---|---|
| `fill_layer` | Fill entire layer with color |
| `fill_selection` | Fill selection (foreground/background/transparent) |
| `fill_rectangle` | Fill a rectangle region |
| `fill_ellipse` | Fill an ellipse region |
| `draw_line` | Draw a line (pencil or paintbrush) |
| `draw_rectangle` | Draw a rectangle outline |
| `draw_ellipse` | Draw an ellipse outline |
| `gradient_fill` | Apply linear or radial gradient |
| `set_colors` | Set foreground/background colors |

### 🔤 Text
| Tool | Description |
|---|---|
| `add_text` | Add a text layer |
| `edit_text` | Edit existing text layer |
| `list_fonts` | List available fonts |

### ✨ Filters & Effects
| Tool | Description |
|---|---|
| `apply_gaussian_blur` | Gaussian blur filter |
| `apply_pixelate` | Pixelate/mosaic effect |
| `apply_emboss` | Emboss effect |
| `apply_vignette` | Vignette darkening |
| `apply_noise` | Add noise/grain |
| `apply_drop_shadow` | Drop shadow effect |

### 📁 File Operations
| Tool | Description |
|---|---|
| `open_image` | Open image file |
| `export_image` | Export to PNG, JPEG, BMP, TIFF |
| `save_xcf` | Save as GIMP XCF (preserves layers and metadata) |
| `new_canvas` | Create blank canvas |
| `close_image` | Close image |
| `list_images` | List open images |
| `set_active_image` | Bring a specific open image to the front |

### 📦 Batch & Multi-Format Export
| Tool | Description |
|---|---|
| `batch_export` | Export all open images (or one) to a directory |
| `batch_resize` | Resize all open images to a common size |
| `export_web_optimized` | Export as JPEG and PNG, keeping the smaller |
| `export_icon_sizes` | Export a full Android or iOS icon set |
| `export_social_media_kit` | Export resized versions for social media platforms |
| `export_sprite_sheet` | Combine frames into a sprite sheet PNG |

### ↩️ History
| Tool | Description |
|---|---|
| `undo` | Undo one or more operations on an image |
| `redo` | Redo previously undone operations |

### 🔍 Info & Context
| Tool | Description |
|---|---|
| `get_image_metadata` | Image size, mode, layers, filename |
| `get_gimp_info` | GIMP version, platform, capabilities |
| `get_context_state` | Current colors, brush, opacity, mode |
| `get_pixel_color` | Color value at a specific pixel |
| `get_histogram` | Histogram data for a channel |
| `get_selection_bounds` | Current selection bounds |

### 🔌 Connection & Raw API
| Tool | Description |
|---|---|
| `check_server` | Check the GIMP plugin socket is reachable and that the plugin version matches the server |
| `restart_server` | Drop and re-establish the connection to the plugin |
| `call_api` | Run arbitrary Python-Fu code inside GIMP (escape hatch for anything not covered above) |

---

## AI Agent Feedback Loop

The `get_state_snapshot` tool enables a pattern where the AI loops until a goal is visually confirmed:

```text
┌─────────────┐
│  Apply edit │
└──────┬──────┘
       │
       ▼
┌─────────────────┐
│ get_state_      │  ← AI sees live PNG, no disk save needed
│ snapshot()      │
└──────┬──────────┘
       │
       ▼
┌─────────────────┐     ┌──────────────────┐
│ Goal achieved?  │─ No─▶ Adjust & retry   │
└──────┬──────────┘     └──────────────────┘
       │ Yes
       ▼
┌─────────────┐
│   Export    │
└─────────────┘
```

### Example: Iterative Background Removal

See [`bg_remove_iterative.py`](bg_remove_iterative.py) for a complete example. The AI:

1. Removes the background using edge-seeded contiguous select
2. Takes a snapshot to check the result
3. Scans for remaining background-colored pixels
4. Runs targeted removal passes with progressively finer grids (25px → 1px)
5. Runs a final despeckle pass for isolated pixels
6. Loops until no background pixels remain

---

## Example Scripts

| Script | Description |
|---|---|
| [`run_tests.py`](run_tests.py) | 59-test suite — run against your GIMP to verify all tools work |
| [`bg_remove_iterative.py`](bg_remove_iterative.py) | Iterative BG removal with snapshot checkpoints |
| [`bg_remove.py`](bg_remove.py) | Simple single-pass background removal |
| [`agent_edit_demo.py`](agent_edit_demo.py) | Full pipeline: open → remove BG → edit expression → export |
| [`tests/test_add_text_metadata.py`](tests/test_add_text_metadata.py) | Regression test: `add_text` returns real layer metadata |
| [`tests/continuous_edit_test/continuous_edit_test.py`](tests/continuous_edit_test/continuous_edit_test.py) | End-to-end agent editing loop: BG removal, selective recolor, composite |

Run the test suite to verify your setup:
```bash
python run_tests.py
# Expected: 59/59 PASSED
```

---

## Technical Architecture

### Plugin ↔ Server Communication
```text
AI Client (Claude, etc.)
      │  MCP (stdio)
      ▼
gimp_mcp_server.py          ← MCP tool definitions
      │  TCP JSON  :9877
      ▼
gimp-mcp-plugin.py          ← Runs inside GIMP process
      │  PyGObject
      ▼
GIMP 3.2 (gi.repository.Gimp)
```

- MCP server translates tool calls into JSON commands sent to the plugin over TCP
- Plugin executes operations directly in the GIMP process via PyGObject
- Two message formats: `{"type": "...", "params": {...}}` for named tools, `{"cmds": ["python..."]}` for arbitrary exec

### GIMP 3.2 Compatibility Notes

GIMP 3.x introduced breaking API changes from GIMP 2.x. Key fixes included in this release:

| Issue | Fix |
|---|---|
| `layer.copy(False)` → error | `layer.copy()` takes no args in GIMP 3.2 |
| `Gimp.text_fontname()` removed | Use PDB `gimp-text-fontname` |
| `gimp-blend` removed | Use GEGL `gegl:linear-gradient` / `gegl:radial-gradient` |
| `GimpDoubleArray` TypeError in curves | Use `drawable.curves_spline()` directly |
| `Gimp.fonts_get_list()` returns `Font` objects | Convert via `.get_name()` before JSON serialization |
| `image.select_none()` removed | Use PDB `gimp-selection-none` |
| `layer.get_pixel()` returns `Gegl.Color` | Use `.get_rgba()` to extract float components |

---

## Troubleshooting

### "Could not connect to GIMP"
- GIMP must be running with an image open
- Start the MCP server: **Tools > MCP > Start MCP Server**, or enable autostart in **Tools > MCP > MCP Server Settings...**
- If you changed the port in GIMP, set `GIMP_MCP_PORT` to the same value in your MCP client config
- Check port 9877 (or your custom port) is not blocked by firewall

### "GIMP did not reply within 300 s"
- The command took longer than the MCP server waits. Raise `GIMP_MCP_TIMEOUT` (seconds) in your MCP client config; see [Timeout for long commands](#timeout-for-long-commands)
- GIMP may still be running the command: check the image before retrying

### Plugin Not Visible in GIMP
- Look under **Tools > MCP** (the plugin adds an `MCP` submenu, not a top-level `Tools` entry)
- Confirm the plugin file is in the correct directory: `uvx gimp-mcp-plugin@latest install-plugin --dry-run` shows where it goes
- **Upgraded GIMP recently?** A minor upgrade (e.g. 3.0 → 3.2) moves the per-user config folder to a new version directory; re-run `uvx gimp-mcp-plugin@latest install-plugin` (use `--dry-run` to see which folder it picks). Verify the active path via **Edit > Preferences > Folders > Plug-ins**.
- On Linux/macOS: ensure the file has execute permission (`chmod +x`)
- Restart GIMP after installation
- Check **Filters > Script-Fu > Console** for error messages

### Tests Failing
Run the test suite and check the failure list:
```bash
python run_tests.py
```
Each failure includes the tool name and error — most issues on GIMP 3.2 are covered by the fixes above.

---

## Example Output

<img src="gimp-screenshot1.png" alt="GIMP MCP Example" width="400">

*"Draw me a face and a sheep" — generated entirely through natural language via GIMP MCP*

---

## Future Enhancements

- **📚 Recipe Collection**: Reusable workflow templates (portrait cleanup, product photo, etc.)
- **🚀 Dynamic Discovery**: Auto-generate MCP tools from GIMP's full PDB procedure database
- **🔒 Security**: Sandboxed execution for untrusted command inputs
- **⚡ Performance**: Optimized bitmap transfer for large images
- **🌐 Remote Access**: Network-accessible GIMP instances

---

## Contributing

Contributions are welcome — bug fixes, new tools, documentation, or example scripts. Open a PR or issue on GitHub.

### Development Setup

Install dev dependencies and activate the pre-commit hook so `ruff` runs on every commit:

```bash
uv sync
uv run pre-commit install
```

After this, `ruff` checks staged files on each `git commit` (with `--fix` applied automatically). The same check runs in CI, so the hook is just a fast local safety net.

To bump the pinned hook versions later:

```bash
uv run pre-commit autoupdate
```

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).
