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) anddarktable-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.
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).
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
-
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/BrewfileUbuntu 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 -
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 -
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 -
Connect your MCP client. It runs the server's command (
.venv/bin/darktable-api-mcp) with two environment variables:DTAPI_BIN(the engine) andDTAPI_CONFIGDIR(the library copy'sconfigfolder); 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 -
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 -
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. -
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).
| 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.
- darktable-api, built from the
darktable fork with
-DUSE_API=ON(off by default) and-DUSE_AI=ONfor 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.mdthere. - 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 needsplugins/ai/ort_library_path(after the Quick start's steps).
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.
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 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
{
"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/ 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/
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).
GPL-3.0, like darktable.