Skip to content
Conduit

mcp server

darktable-api-mcp

MCP server that lets an AI browse a darktable library and edit photos through darktable-api, a headless darktable

mmcc-xx/darktable-api-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "darktable-api-mcp": {
      "command": "uvx",
      "args": [
        "darktable-api-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 21, 2026

darktable-api-mcp

An MCP server that lets an AI assistant (e.g. Claude, ChatGPT) browse a darktable library and edit photos. All the work is done by darktable-api, darktable's own code running as a long-lived headless engine. The engine renders previews, applies module settings and writes the history to the library exactly as darktable does. darktable's own window (built from the same fork) can serve the API as well instead, sharing the photo open in its darkroom live. A few tools require running darktable with the GUI (the color pickers and auto buttons, and switching darktable's darkroom to a photo), but most edits can be done headlessly. No Lua script or XMP editing is involved.

Demo Video

What's included:

  • 78 MCP tools (list): browse the library, rate, label and tag photos; open a photo and change any module's settings, with previews to check each step; presets and styles; crop and rotate; masks (drawn shapes, parametric ranges, AI object masks); curves; finding and healing sensor dust and other retouching; AI denoise; before/after comparisons and alternative versions to choose from; export.
  • 6 skills that teach the AI whole tasks: darktable-edit (a full develop in darktable's scene-referred workflow), darktable-mono (black and white), darktable-local (masks, dodge and burn), darktable-cleanup (dust, noise, sharpening), darktable-batch (one look across several photos) and darktable-review (a critique with concrete suggestions, without changing anything).

This is experimental. I recommend using it on a copy of your library (the copy script below does that). Saving writes darktable's history in the engine's module versions, which an older darktable may not be able to read.

Written with AI assistance (Claude), directed and reviewed by the repository owner.

Quick start: engine, MCP server and web app

These instructions will get the fork of darktable with the API, a web app, and the MCP server built and running. This has been tested on macOS (Apple Silicon) and Ubuntu 24.04; other Linux distributions need the equivalent packages (darktable's README links build instructions for Fedora and others). Python 3.10 or later.

Which macOS: an Apple Silicon Mac on a macOS version Homebrew still makes packages for: the latest macOS and the two before it (in October 2026: macOS 27, 26 and 15; see Homebrew's support tiers). On an older one, macOS 14 included, Homebrew has no packages for darktable's dependencies and brew bundle has to build about 20 of them from source, which is slow and may fail; update macOS first. Intel Macs are untested (Homebrew supports them less: its packages may be missing there too). Tested on macOS 27 and 26 (the latter on GitHub's macos-latest runner).

Pick a branch

Pick the branch of the fork matching the darktable you edit your library with (a newer darktable writes module versions an older one can't open):

Your darktableBranchWhat it is
the latest release, 5.6.x (5.6.2 on)darktable-api-5.6darktable's 5.6 release with the API; darktable's point releases are merged in as they come out. Most people want this one
built from masterdarktable-api (the fork's default branch)darktable's development version with the API; darktable master is merged in weekly once the tests pass. Its edits use module versions darktable 5.6 can't open. Has the newest tools first (ai_fill, the AI fill in heal_dust_spots)

When darktable releases a new version (5.8, ...), a darktable-api-5.8 branch starts from it and the previous release branch is frozen (tagged api-dt<version>-final, no more changes): pick the newest darktable-api-<version>.

Set it in the shell you run the steps below in:

BRANCH=darktable-api-5.6     # latest release (for master: BRANCH=darktable-api)

Everything else is the same for both branches.

Steps

  1. Install the build dependencies.

    macOS:

    Install Homebrew, then:

    xcode-select --install
    mkdir -p ~/src && cd ~/src
    git clone -b $BRANCH --recurse-submodules https://github.com/mmcc-xx/darktable.git
    brew bundle --file=darktable/.ci/Brewfile
    

    Ubuntu 24.04:

    sudo apt install git build-essential python3-venv cmake ninja-build \
      intltool xsltproc libgtk-3-dev libjson-glib-dev liblcms2-dev \
      libexiv2-dev liblensfun-dev libcurl4-openssl-dev libsqlite3-dev \
      libxml2-dev libpugixml-dev librsvg2-dev libjpeg-dev libtiff-dev \
      libopenexr-dev libwebp-dev libavif-dev libheif-dev libjxl-dev \
      liblua5.4-dev libcolord-gtk-dev libgmic-dev libportmidi-dev \
      libsecret-1-dev libraw-dev libgphoto2-dev libgraphicsmagick1-dev \
      libopenjp2-7-dev libosmesa6-dev ocl-icd-opencl-dev libsdl2-dev \
      libcups2-dev libpotrace-dev libarchive-dev
    mkdir -p ~/src && cd ~/src
    git clone -b $BRANCH --recurse-submodules https://github.com/mmcc-xx/darktable.git
    
  2. Build the engine (darktable-api and a darktable from the same fork; 10–20 minutes the first time, the same on both systems):

    cd ~/src/darktable
    cmake -B build -G Ninja -DUSE_API=ON -DUSE_AI=ON
    cmake --build build
    
  3. Install the MCP server and copy your library. The copy is the library everything below works on; your own library and XMP files are never written:

    cd ~/src
    git clone https://github.com/mmcc-xx/darktable-api-mcp.git
    cd darktable-api-mcp
    python3 -m venv .venv && .venv/bin/pip install -e '.[dust]'
    .venv/bin/python make_library_copy.py    # ~/.config/darktable -> ./library-copy
    
  4. Connect your MCP client. It runs the server's command (.venv/bin/darktable-api-mcp) with two environment variables: DTAPI_BIN (the engine) and DTAPI_CONFIGDIR (the library copy's config folder); Install has a JSON example for clients configured that way. With Claude Code, for example, which also loads the skills (link them wherever your client looks for skills):

    claude mcp add darktable-api \
      -e DTAPI_BIN=$HOME/src/darktable/build/bin/darktable-api \
      -e DTAPI_CONFIGDIR=$HOME/src/darktable-api-mcp/library-copy/config \
      -- $HOME/src/darktable-api-mcp/.venv/bin/darktable-api-mcp
    mkdir -p ~/.claude/skills
    for s in ~/src/darktable-api-mcp/skills/*/; do ln -s "$s" ~/.claude/skills/; done
    
  5. Run the web app on the same library copy, so the browser and the AI share one engine and one edit:

    cd ~/src
    git clone https://github.com/mmcc-xx/darktable-api-web.git
    cd darktable-api-web
    python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
    DTAPI_BIN=$HOME/src/darktable/build/bin/darktable-api \
    DTAPI_CONFIGDIR=$HOME/src/darktable-api-mcp/library-copy/config \
        .venv/bin/uvicorn dtweb.main:app --port 8020 --timeout-graceful-shutdown 3
    

    Open http://127.0.0.1:8020

  6. Try it. Start your client, check it lists the darktable-api server (in Claude Code: /mcp), and ask e.g. "list my last film roll" or "open photo 12 and do a basic edit". The web page follows along live.

  7. Optional: darktable's own window on the same library, shared live with the browser and the AI:

    ~/src/darktable/build/bin/darktable \
      --configdir ~/src/darktable-api-mcp/library-copy/config \
      --cachedir ~/src/darktable-api-mcp/library-copy/cache \
      --api-socket ~/src/darktable-api-mcp/library-copy/darktable-api.sock
    

The AI mask and AI denoise tools need darktable's AI models: download them in that darktable's preferences (AI tab).

AI fill (ai_fill, and the fills in heal_dust_spots; the darktable-api branch only) fills on this machine with an inpainting model darktable's preferences don't offer. make_inpaint_models.py downloads LaMa (~208 MB, the default, better on fine texture such as grass) and MI-GAN (smaller, used when LaMa is missing) from Hugging Face, checks them and writes them into darktable's models folder:

cd ~/src/darktable-api-mcp
python3 -m venv ~/src/onnx-venv && ~/src/onnx-venv/bin/pip install onnx onnxruntime numpy
~/src/onnx-venv/bin/python -I make_inpaint_models.py ~/.local/share/darktable/models

(--only lama or --only migan for one of them. If you set plugins/ai/models_path in darktablerc, use that folder instead.) The virtual environment is only needed for this step; delete it afterwards.

ai_fill can also fill online, at Black Forest Labs: model="flux-erase" (FLUX Erase, for removing things; real texture such as fur) or model="flux" (FLUX.1 Fill, paints what belongs there, guided by a prompt) or model="flux3" (FLUX 3 Image: no mask, told in prompt what to remove; it re-renders the crop and only the shape is used). Only the crop around the shape is uploaded, and each fill uses credits from your BFL account. Make an account and an API key at bfl.ai, then give the MCP server the key, either as BFL_API_KEY in its environment (-e BFL_API_KEY=... in step 4) or in a file only you can read:

mkdir -p ~/.config/darktable-api-mcp
(umask 077; printf '%s\n' 'YOUR-KEY' > ~/.config/darktable-api-mcp/bfl_api_key)

Fills stay local unless a FLUX model is asked for; without a key that fails with "no Black Forest Labs API key".

Linux: darktable run from the build folder can't find ONNX Runtime (it loads it by file name), so the AI tools fail until you point the library copy at it, with darktable's window closed:

echo "plugins/ai/ort_library_path=$(ls ~/src/darktable/build/_deps/onnxruntime/lib/libonnxruntime.so.*.*.*)" \
  >> ~/src/darktable-api-mcp/library-copy/config/darktablerc

(or sudo cmake --install build in ~/src/darktable, which installs this darktable over any packaged one). darktable's window on Linux needs a desktop session; the engine, MCP server and web app run headless, e.g. on a server over SSH, with the web app reached through an SSH tunnel (ssh -L 8020:127.0.0.1:8020 server).

Tools

ToolDoes
list_film_rolls, list_images, image_infobrowse: film rolls; photos filtered by film roll, rating, color label, with paging; a photo's camera data
get_thumbnaildarktable's thumbnail of a photo (image)
import_photosimport files or folders (e.g. a card's DCIM) into the library, in place or copied into darktable's import folders; photos already there skipped; a background job
make_thumbnailsthumbnails in bulk (photos, a film roll, the whole library) rendered into darktable's thumbnail cache, as a background job
get_photo_metadata, list_tags, set_tags, set_metadata, set_locationtags, metadata (title, description, creator, ...) and location, as darktable's tagging, metadata editor and geotagging
photo_sheet, tag_photostagging by content: pages of thumbnails with each photo's id, date, camera, place and tags for the AI to look at, then each photo's own tags applied in one call
set_rating, set_color_labelrate (0–5), reject, color labels, as darktable's lighttable
open_photoopen a photo for editing (joins an edit already open in another app, unsaved changes included)
open_in_darkroom, get_collectionswitch darktable's darkroom to a photo (needs darktable's window); the photos darktable's lighttable shows, and the selection
check_sensor_clippingreal sensor clipping, from the raw file, per color, with each color's headroom (needs the [dust] extra)
open_darkroom_photoopen the photo the user has open in darktable's darkroom (needs darktable's window)
list_modules, get_modulethe photo's modules; a module's changed settings with defaults, ranges and dropdown values, the rest by value (full=True: all details)
set_module, enable_modulechange settings by name (checked, all or nothing), lists whole or by element (grey[1]), module on/off; preview=True / measure={...} (also on apply_preset, set_blending, set_curve, crop_photo) return the render or readout in the same call
list_presets, apply_preseta module's presets, applied as darktable's presets menu does (e.g. "monochrome | luminance-based")
get_history, set_history_endhistory steps; undo/redo to a step
add_module_instance, rename_module_instance, remove_module_instancemodule instances (e.g. a second color calibration for creative B&W)
move_modulemove a module in the pipe (e.g. a second color calibration after the tone mapper)
get_curve, set_curvecurves of rgbcurve, tonecurve, colorzones, basecurve
duplicate_photo, remove_photosa duplicate (version) of a photo, with its edit or virgin; remove photos from the library as the lighttable's remove does (the files stay; asks for confirm)
compress_historycompress (or truncate) the history as darktable's history panel does; saves first
get_geometry, rotate_photo, crop_photoorientation, straightening (with the automatic crop) and crop, through darktable's flip, rotate and perspective, and crop modules
export_photodarktable's export of the saved edit to a file (format, size, quality, output pattern, style; the user's export settings otherwise)
get_blending, set_blendinga module's blending: blend mode, opacity, parametric ranges per channel in the darkroom's units (e.g. denoise only the shadows)
add_ai_maskdarktable's AI object mask: points on a subject, outlined into path shapes on a module (needs darktable built with AI, AI enabled)
list_markers, set_marker, remove_markernamed points and areas on a photo (the user draws them in the web app; Claude can mark a spot too), saved with the library, not in the edit. add_mask, ai_fill, use_picker take marker=name, measure_photo markers=[...], retouch_spots a spot's "marker"
add_mask, list_masks, remove_maskdrawn shapes (circle, ellipse, gradient, path, brush stroke) on a module, placed on the photo as shown; set_blending(raster_source=...) reuses another module's mask
measure_photovalues at points and in boxes, the histogram (channels=True: per channel) and clipping of the rendered photo
zone_mapthe rendered photo reduced to Ansel Adams's eleven print values (zones 0-X, steps of 10 in Lab L), with the share of the photo in each zone, zones at points, and chosen zones marked in red (needs the [dust] extra)
ai_denoise, job_status, list_jobs, cancel_jobdarktable's AI raw denoise as a background job: a new DNG photo beside the original, in its group (needs darktable built with AI)
ai_fillremove an object or blemish: an AI inpainting model (LaMa, or MI-GAN) fills a drawn shape, or the outline of an add_ai_mask module (like_mask_of), from its surroundings; shown by a new composite instance (non-destructive; needs the inpaint model installed: Quick start, after step 7) Small to medium shapes (up to about 1300 px across); larger ones are warned about (a soft smear). In darktable's window too: draw the area as a mask on any module, then like_mask_of (like_mask_shapes for some of its shapes, by name). Online, model="flux-erase" (FLUX Erase, for removals: real texture such as fur) "flux" (FLUX.1 Fill, paints what belongs there, with a prompt) or "flux3" (FLUX 3 Image, told in prompt what to remove) at Black Forest Labs: only the crop around the shape is uploaded; needs your BFL key in BFL_API_KEY or ~/.config/darktable-api-mcp/bfl_api_key. darktable-api on master now, the release branch from 5.8 on (not 5.6).
find_dust_spots, heal_dust_spotssensor dust: a map of dust recurring across the photo's film roll (read from the raw files, cached; small specks and large soft blobs), rated in this photo (raw files that are missing or unreadable are skipped and listed; with too few left the result says the map can't find dust); this_photo=True (on by default for a black and white edit) also finds dust in this photo alone, in darktable's render of the current edit (one-off spots, or faint ones a black and white red filter brings out; numbered from 101, with their own crop sheet, healed only when picked by id); heal circles in retouch as one history step (each source kept clear of the dust by the circle's feathered border), or an AI fill (ai_fill, darktable master) where no clean source is nearby (method: auto, heal, ai_fill), checked at 100% before and after. Needs pip install 'darktable-api-mcp[dust]'
retouch_spots, list_retouch_spots, edit_retouch_spot, remove_retouch_spotsretouch circles that clone, heal, blur or fill, placed on the photo as shown; move, resize, change or delete them
list_styles, create_style, apply_style, delete_styledarktable's styles: make one from a photo's edit, apply one to photos (e.g. one B&W look on a set), as the lighttable does
paste_editone photo's edit, or some of its modules, onto other photos (append or overwrite), as darktable's copy and paste
list_pickers, use_pickera module's color pickers and auto buttons (exposure's picker, color calibration's white balance picker, AgX's auto tune levels, tone equalizer's wands, ...), used as clicking them in darktable: on the photo in darktable's darkroom (needs darktable's window); most pickers also headless (list_pickers shows which)
render_previewdarktable's rendering of the current, unsaved edit (image); uncropped=True shows the whole photo to choose a crop on; zoom=1 a region at 100%; history_step an earlier step (before/after)
compare_before_after, mark_beforebefore and after of the edit (both renders, for the AI), shown to the user as a before/after slider in the web app and, for the photo in darktable's darkroom, a snapshot in darktable's snapshots panel split against the edit (when darktable's window serves the library); "before" is the step before the AI's first edit of the photo, or where mark_before put it
propose_alternatives, apply_alternative, keep_alternativelabeled alternatives (crops, B&W filters, looks: changes as calls of the editing tools) rendered without touching the edit, shown to the user as a strip in the web app; apply the one picked, or keep it as a duplicate photo with its version name set
save, discard_changes, start_overwrite the edit to the library; reopen as saved; delete the edit and apply darktable's defaults again (asks for confirm)
library_status, release_library, acquire_library, takeover_libraryhand the library to darktable's GUI and take it back; takeover_library asks a running darktable to quit the normal way (asks for confirm, never kills it)

Edits stay in memory until save. Errors come back as MCP tool errors with darktable's message, e.g. 'exposure': 99 is outside -18..18. The engine also sends an error code and name (not_found, invalid_params, busy, unsaved_changes, ...; see its src/api/README.md): the server retries busy errors a few times (0.5, 1 and 2 s apart) and reopens a photo the engine had closed (not_open).

Measured on an Apple M1 (CPU only) with a 20 MP raw: render_preview after a change 0.15–0.4 s at 1200 px; browsing calls a few milliseconds; the engine starts in about 3 s on the first call.

Requirements

  1. darktable-api, from the darktable fork https://github.com/mmcc-xx/darktable (see src/api/README.md there). Build darktable with the API enabled (-DUSE_API=ON, off by default), which builds darktable-api alongside, and with AI for the AI mask and AI denoise tools:

    Which branch: see Pick a branch (darktable-api-5.6 for the latest release, darktable-api for master).

    git clone -b darktable-api-5.6 --recurse-submodules https://github.com/mmcc-xx/darktable.git    # or -b darktable-api
    cd darktable
    cmake -B build -G Ninja -DUSE_API=ON -DUSE_AI=ON
    cmake --build build
    

    darktable's README lists the build dependencies (on macOS: brew bundle --file=.ci/Brewfile; on Ubuntu 24.04 also libpotrace-dev and libarchive-dev). The AI build downloads ONNX Runtime itself; on Linux, darktable run from the build folder can't find it (it loads it by file name): cmake --install build, or set plugins/ai/ort_library_path in the library copy's darktablerc to build/_deps/onnxruntime/lib/libonnxruntime.so.<version>. The AI models are downloaded from darktable's preferences (AI tab), except the AI fill models (make_inpaint_models.py, after the Quick start's steps).

  2. Python 3.10 or later.

Updating the engine

The branches are only ever added to (darktable's changes come in as merges, never by rewriting history), so a pull updates a clone:

cd ~/src/darktable
git pull --recurse-submodules
cmake --build build

Then reconnect the MCP server (or restart the web app). A clone of darktable-api made before 2026-10-13 (the branch was rebased until then) may refuse to pull; reset it once to the published branch (this drops local commits): git fetch origin && git reset --hard origin/darktable-api.

Install

git clone https://github.com/mmcc-xx/darktable-api-mcp.git
cd darktable-api-mcp
python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/python make_library_copy.py      # ~/.config/darktable -> ./library-copy

make_library_copy.py copies library.db, data.db and darktablerc (safe while darktable is running) and sets write_sidecar_files=never in the copy, so nothing done here touches the XMP files next to your photos. Your photos are only read.

Variable
DTAPI_BINrequired unless on the PATHthe darktable-api binary
DTAPI_CONFIGDIRrequiredthe darktable config dir to use: the copy's config folder
DTAPI_CACHEDIRdefault: next to it, cachethe engine's darktable cache
DTAPI_SOCKETdefault: darktable-api.sock next to the config dir (or /tmp/darktable-api-<uid>-<hash>.sock if that path is too long)where the engine listens; every app using the library must use the same one
DTAPI_VARIANTS_DIRdefault: ~/.cache/darktable-api-mcp/variantswhere propose_alternatives renders the alternatives (the web app serves them from there: set it the same for both)
DTAPI_FILLS_DIRdefault: unset, a darktable_fills folder beside each photowhere ai_fill (and heal_dust_spots' fills) write their patch TIFFs: set it for a library copy, whose photos are someone's originals
BFL_API_KEYoptional; else ~/.config/darktable-api-mcp/bfl_api_keyyour Black Forest Labs key, for ai_fill's online FLUX models only
DTAPI_GUI_BINdefault: darktable next to DTAPI_BINthe only darktable takeover_library may quit

Claude Code

claude mcp add darktable-api \
  -e DTAPI_BIN=/path/to/darktable/build/bin/darktable-api \
  -e DTAPI_CONFIGDIR=/path/to/darktable-api-mcp/library-copy/config \
  -- /path/to/darktable-api-mcp/.venv/bin/darktable-api-mcp

Claude Desktop (or another MCP client)

{
  "mcpServers": {
    "darktable-api": {
      "command": "/path/to/darktable-api-mcp/.venv/bin/darktable-api-mcp",
      "env": {
        "DTAPI_BIN": "/path/to/darktable/build/bin/darktable-api",
        "DTAPI_CONFIGDIR": "/path/to/darktable-api-mcp/library-copy/config"
      }
    }
  }
}

Skills

skills/ holds Agent Skills that teach the AI how to use the tools for whole tasks (for clients that load skills, such as Claude Code): darktable-edit (darktable's standard scene-referred workflow), darktable-mono (black and white, with film filter mixes) and darktable-cleanup (dust, retouching, noise, sharpening, fringes), darktable-local (masks and local edits), darktable-batch (one look across several photos) and darktable-review (critique and suggestions without changing anything). Link them where your client finds skills; for Claude Code, that is ~/.claude/skills/ (all projects) or a project's .claude/skills/:

ln -s /path/to/darktable-api-mcp/skills/darktable-edit ~/.claude/skills/

Sharing the engine with a web app

The server doesn't run darktable itself: it connects to a darktable-api engine on a unix socket next to the library copy, and starts one if none is running. darktable-api-web pointed at the same library copy uses the same engine, so you can watch in the browser while the AI edits, and both work on the same photos: a photo open in both is one shared edit (open_photo reports "shared": true), the web page updates live, and save saves the whole edit. The engine keeps up to 3 photos open and stops 10 minutes after the last client disconnected, unless something is unsaved.

darktable's own window can serve the library as well: started (from the same fork) with --api-socket pointing at the same socket, it takes over from the engine automatically, unsaved edits included, and the photo open in its darkroom is shared live: the AI's changes move darktable's sliders, and the user's changes in darktable reach the AI. When darktable quits, the server goes back to the engine. (release_library / acquire_library are for a darktable started without --api-socket.)

Tested on macOS 27 (Apple M1) and Linux (Ubuntu 24.04): the engine's test suite, headless and with darktable's window, and Claude Code sessions with the skills. On Linux, takeover_library uses darktable's D-Bus Quit method (untested).

License

GPL-3.0, like darktable.

↑↓ navigate openesc close