Skip to content

About

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

Resources

Stars

7 stars

Watchers

2 watching

Forks

Repository files navigation

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 need darktable's window (switching its darkroom to a photo, a few auto buttons such as the tone equalizer's wands); everything else, most color pickers included, works headless. No Lua script or XMP editing is involved.

darktable-api is three repositories: the darktable fork (the engine), this MCP server (and the setup instructions for all three) and darktable-api-web (a web app). Demo video

What's included:

  • 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

Use darktable-api, the fork's default branch: darktable's development version with the API, merged with darktable master weekly once the tests pass. Its edits use module versions darktable 5.6 can't open, so edit the library only with the darktable from the same build (and work on a copy first).

Branch What it is
darktable-api (default) darktable master with the API; every tool in this server
darktable-api-5.6 frozen (tag api-dt5.6.2-final, 2026-10-29): darktable 5.6.2 with the API as of 2026-10-15, no fixes or new methods since. Its edits open in stock darktable 5.6, but many newer tools report that the engine doesn't support them

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

BRANCH=darktable-api

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

Tool Does
list_film_rolls, list_images, image_info browse: film rolls; photos filtered by film roll, rating, color label, with paging; a photo's camera data
get_thumbnail darktable's thumbnail of a photo (image)
import_photos import 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_thumbnails thumbnails 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_location tags, metadata (title, description, creator, ...) and location, as darktable's tagging, metadata editor and geotagging
photo_sheet, tag_photos tagging 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_label rate (0–5), reject, color labels, as darktable's lighttable
open_photo open a photo for editing (joins an edit already open in another app, unsaved changes included)
open_in_darkroom, get_collection switch darktable's darkroom to a photo (needs darktable's window); the photos darktable's lighttable shows, and the selection
check_sensor_clipping real sensor clipping, from the raw file, per color, with each color's headroom (needs the [dust] extra)
solve_exposure, solve_highlights set exposure so a region (points, boxes, markers) reaches a target Lab L, and the tone mapper's white point (agx, filmic rgb, sigmoid) so no more than a given percent of the render clips, each in one call of a few small readouts and one history step; solve_highlights reports sensor clipping (check_sensor_clipping) instead of changing anything it can't fix
make_panorama stitch photos with Hugin (needs its command-line tools: DTAPI_HUGIN_BIN or ~/.local/hugin/bin): first call returns what would make seams show (lens correction, white balance, per-frame tone mapping ...) and a question; the second, with fixes, stitches virtual copies into a 16-bit Deflate TIFF beside the frames, imported, tagged panorama|from ... and grouped
open_darkroom_photo open "this photo": the one in darktable's darkroom, or with the headless engine the one the web app last opened (library_status web_imgid)
list_modules, get_module the photo's modules; a module's changed settings with defaults, ranges and dropdown values, the rest by value (full=True: all details)
auto_capture_sharpen capture sharpening on, radius and threshold measured on this photo
set_module, enable_module change 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_preset a module's presets, applied as darktable's presets menu does (e.g. "monochrome | luminance-based")
get_history, set_history_end history steps; undo/redo to a step
add_module_instance, rename_module_instance, remove_module_instance module instances (e.g. a second color calibration for creative B&W)
move_module move a module in the pipe (e.g. a second color calibration after the tone mapper)
get_curve, set_curve curves of rgbcurve, tonecurve, colorzones, basecurve
duplicate_photo, remove_photos a 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_history compress (or truncate) the history as darktable's history panel does; saves first
get_geometry, rotate_photo, crop_photo orientation, straightening (with the automatic crop) and crop, through darktable's flip, rotate and perspective, and crop modules
export_photo darktable's export of the saved edit to a file (format, size, quality, output pattern, style; the user's export settings otherwise)
get_blending, set_blending a module's blending: blend mode, opacity, parametric ranges per channel in the darkroom's units (e.g. denoise only the shadows)
add_ai_mask darktable'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_marker named 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_mask drawn 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_photo values at points and in boxes, the histogram (channels=True: per channel) and clipping of the rendered photo
print_size how large the open photo prints from the detail it holds: renders 2-4 detailed regions (or markers) at 100%, measures a usable-detail factor (a shrink-and-scale-back test against a sharp copy of each region) and edge width, adds diffraction from the aperture and pixel pitch, and returns nominal, critical, normal, wall and viewing-distance sizes in inches and cm, with causes and fixes. Needs the dust extra (numpy, OpenCV)
immich_open, immich_inbox, immich_albums, immich_send edit photos from an Immich server and send the edits back, through its API (Immich itself isn't changed): immich_open downloads an Immich original (JPEG or raw) into DTAPI_IMMICH_DIR/<year>/, imports and opens it (again: the same photo); immich_inbox lists, or imports, the photos the user put in the album "Edit in darktable"; immich_send uploads a JPEG export of the saved edit into albums (existing or new); over an Immich original it takes the original's albums, favorite and date and is stacked on top of it (the original stays), and it leaves the inbox; a resend takes the previous upload's place and moves that one to Immich's trash (never anything else). The links are private darktable tags (immich|source|<id>, immich|export|<id>), left out of exports. Needs an API key and IMMICH_URL (below)
zone_map the 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)
analyze_cast the colour cast of the render: median a*/b* of likely-neutral pixels per tonal band (or of a gray card box / marker), with a warm/cool/magenta/green verdict; reads the toning of a black and white photo (needs the [dust] extra)
change_map what changed between two history steps: per grid cell the mean dE and EV change, the share changed, where, and with boxes / markers / a module's mask the change inside vs outside (leak); optional heatmap (needs the [dust] extra)
ai_denoise, job_status, list_jobs, cancel_job darktable's AI raw denoise as a background job: a new DNG photo beside the original, in its group (needs darktable built with AI)
ai_fill remove 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. Needs the darktable-api branch (not 5.6)
find_dust_spots, heal_dust_spots sensor 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-api branch) 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_spots retouch circles that clone, heal, blur or fill, placed on the photo as shown; move, resize, change or delete them
fix_red_eye red eye from points or a marker (an area is searched for red round pupils): desaturate (a masked color balance rgb instance, catchlight kept) or fill (the local AI model, then a dark pupil and catchlight); auto picks; pets' eyeshine needs fill
list_styles, create_style, apply_style, delete_style darktable'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_edit one photo's edit, or some of its modules, onto other photos (append or overwrite), as darktable's copy and paste
list_pickers, use_picker a 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_preview darktable'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_before before 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_alternative labeled 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_over write 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_library hand 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

  • darktable-api, built from the darktable fork with -DUSE_API=ON (off by default) and -DUSE_AI=ON for the AI tools: the Quick start's steps 1 and 2, which need the build dependencies darktable's README lists. Branch: Pick a branch. The engine's own documentation: src/api/README.md there.
  • Python 3.10 or later.
  • AI models: the AI mask and AI denoise models from that darktable's preferences (AI tab); the AI fill models from make_inpaint_models.py (after the Quick start's steps). On Linux, ONNX Runtime needs plugins/ai/ort_library_path (after the Quick start's steps).

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 '.[dust]'
.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_BIN required unless on the PATH the darktable-api binary
DTAPI_CONFIGDIR required the darktable config dir to use: the copy's config folder
DTAPI_CACHEDIR default: next to it, cache the engine's darktable cache
DTAPI_SOCKET default: 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_DIR default: ~/.cache/darktable-api-mcp/variants where propose_alternatives renders the alternatives (the web app serves them from there: set it the same for both)
DTAPI_FILLS_DIR default: unset, a darktable_fills folder beside each photo where 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_KEY optional; else ~/.config/darktable-api-mcp/bfl_api_key your Black Forest Labs key, for ai_fill's online FLUX models only
BFL_API_URL optional; default https://api.bfl.ai/v1 Black Forest Labs' API address (tests point it at a fake server)
IMMICH_URL for the Immich tools; else an IMMICH_URL= line in ~/.config/darktable-api-mcp/immich.conf your Immich server's API, e.g. https://immich.example.com/api
IMMICH_API_KEY for the Immich tools; else ~/.config/darktable-api-mcp/immich an Immich API key (permissions: album.read, album.create, albumAsset.create, albumAsset.delete, asset.read, asset.view, asset.download, asset.upload, asset.update, asset.copy, asset.delete, stack.create, stack.update)
IMMICH_PANGOLIN optional; default ~/.config/darktable-api-mcp/immich-pangolin a file of P_ACCESS_TOKEN_ID= / P_ACCESS_TOKEN= lines: a Pangolin share link's headers, for an Immich behind Pangolin's sign-in
IMMICH_INBOX, DTAPI_IMMICH_DIR optional (or immich.conf lines); default Edit in darktable, ~/Pictures/immich the inbox album's name; where Immich originals are downloaded to (a folder per year)
DTAPI_GUI_BIN default: darktable next to DTAPI_BIN the only darktable takeover_library may quit
DTAPI_MAX_SESSIONS, DTAPI_IDLE_EXIT 3, 600 photos the engine keeps open, and seconds it runs after the last client left; used when this server starts the engine
DTAPI_DUST_CACHE default: ~/.cache/darktable-api-mcp/dust find_dust_spots' per-roll dust maps
DTAPI_HUGIN_BIN default: ~/.local/hugin/bin, else the PATH Hugin's command-line tools, for make_panorama

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.

About

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

Resources

Stars

7 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages