Skip to main content
Glama

Lightroom Classic MCP

Lightroom Classic MCP banner

Agentic professional photo editing for Adobe Lightroom Classic on macOS.

This project combines a Lightroom Classic plugin bundle and a Python MCP server so Codex, Claude, and other MCP clients can operate Lightroom through Lightroom itself. The goal is simple: expose serious editing and catalog workflows without touching the catalog database directly.

This project is not affiliated with or endorsed by Adobe.

Install In 2 Minutes

If you want to use this with Codex or Claude, start here:

git clone https://github.com/4xiomdev/lightroom-classic-mcp.git
cd lightroom-classic-mcp
./scripts/install_for_ai.sh --client both

That one command:

  • installs the Lightroom plugin bundle

  • bootstraps the Python runtime

  • registers the MCP server with Codex

  • registers the MCP server with Claude

By default, the registered MCP command expects Lightroom to already be open. That avoids surprising app launches when Codex or Claude probes the server.

Homebrew install:

brew tap 4xiomdev/tap
brew install lightroom-classic-mcp
lightroom-classic-mcp-install --client both

If you prefer the old behavior where the MCP command opens Lightroom for you, use --auto-launch during install.

Related MCP server: Lightroom MCP Elite

Use With Codex

Fast path:

./scripts/install_for_ai.sh --client codex

Manual Codex registration:

codex mcp add lightroom-classic -- bash -lc 'cd "/absolute/path/to/lightroom-classic-mcp" && ./scripts/start_managed_server.sh'

Use With Claude

Fast path:

./scripts/install_for_ai.sh --client claude

Manual Claude registration:

claude mcp add -s local lightroom-classic -- bash -lc 'cd "/absolute/path/to/lightroom-classic-mcp" && ./scripts/start_managed_server.sh'

More detail: docs/CLIENT_SETUP.md

Launch Behavior

The MCP wrapper does not auto-open Lightroom by default.

That is intentional. During clean-install testing, automatic app launch turned out to be noisy and surprising, especially when clients probe MCP servers in the background.

Default behavior:

  • open Lightroom yourself

  • then let Codex or Claude connect through MCP

If you want the MCP wrapper to launch Lightroom for you, install with:

./scripts/install_for_ai.sh --client both --auto-launch

Why This Exists

Lightroom Classic is still the center of a lot of real photo workflows, but it is hard to automate safely from external tools. This project gives an AI agent a controlled way to:

  • inspect the current selection

  • read and write metadata

  • read and write Develop settings

  • apply grouped looks and presets

  • work with masks, collections, snapshots, exports, and virtual copies

The key design choice is that Lightroom still performs the actual work. The Python side acts as a bridge and validation layer, not as a catalog editor.

What This Feels Like

This is meant to feel like a professional photo editing operator for Lightroom Classic:

  • inspect selected photos and catalog state

  • inspect the original file directly from the Lightroom-provided path

  • apply structured edit changes safely

  • read and write Develop settings through Lightroom

  • automate repetitive editing workflows

  • stay local, deterministic, and compatible with real Lightroom usage

Inspection-First Workflow

The preferred workflow is:

  1. ask Lightroom for the active photo or current selection

  2. use the returned absolute file path to inspect the original image directly

  3. decide your edit

  4. apply Lightroom changes through MCP

  5. export only if you need a rendered before/after or final output

New inspection tools:

  • get_active_photo_file

  • get_selected_photo_files

These are read-only MCP tools that return Lightroom metadata plus a normalized inspection payload:

  • absolute file path

  • local ID

  • filename

  • dimensions when available

  • file existence / readability / inspectability flags

  • basic file metadata like suffix, MIME type, and size when readable

Example Agent Prompts

Inspect the active image before editing:

Use get_active_photo_file, inspect the image at the returned path, then tell me what edit you would make before changing anything in Lightroom.

Snapshot first, then make a targeted edit:

Use get_active_photo_file to inspect the active image, create a Lightroom snapshot, then lift the subject slightly without blowing out highlights.

Use export only for verification:

Inspect the active image from its original file path, make the edit in Lightroom, then export a verification JPEG so we can compare before and after.

Restore if needed:

If the edit is not an improvement, restore the most recent Lightroom snapshot instead of trying to manually undo each slider.

Who This Is For

  • photographers building agentic editing workflows around Lightroom Classic

  • creative technologists connecting Codex or Claude to a real editing environment

  • developers who want a local-first Lightroom MCP server that is installable and scriptable

How It Works

lightroom-classic-mcp is split into three parts:

  1. a Lightroom plugin bundle in plugin/LightroomMCPCustom.lrplugin

  2. a localhost socket bridge implemented inside Lightroom

  3. a Python MCP server in src/lightroom_mcp_custom/

When the plugin starts, it opens localhost sockets and writes bridge metadata to /tmp/lightroom_mcp_custom_ports.json. The managed server launcher waits for that handshake and then starts the MCP server over stdio.

More detail: docs/ARCHITECTURE.md

Requirements

  • macOS

  • Adobe Lightroom Classic installed locally

  • Python 3.10+

  • An MCP client that can launch a local command

What You Get

  • non-destructive Lightroom SDK-driven edits

  • localhost-only bridge

  • validation before Develop writes

  • a one-command managed startup path

  • a repo layout that keeps the Lightroom plugin and MCP server together

  • direct file-path inspection helpers for active and selected photos

Repository Layout

  • plugin/LightroomMCPCustom.lrplugin/ - Lightroom plugin bundle

  • src/lightroom_mcp_custom/ - Python MCP server and bridge client

  • scripts/install_plugin.sh - installs the plugin bundle into Lightroom's plugin folders

  • scripts/start_managed_server.sh - recommended launcher for daily use

  • scripts/run_server.sh - starts the MCP server directly

  • scripts/smoke_bridge.py - verifies local bridge connectivity

  • scripts/print_mcp_config.sh - prints an MCP config snippet for the current checkout path

  • scripts/package_release.sh - builds source and plugin zip bundles in dist/

Install From A Release

If you downloaded a release zip instead of cloning the repo:

  1. Unzip it anywhere on your Mac.

  2. Run ./scripts/install_for_ai.sh --client both.

If you only want one client, use --client codex or --client claude.

Quick Start

  1. Clone the repo anywhere on your Mac.

git clone <your-repo-url>
cd lightroom-classic-mcp
  1. Run the guided installer.

./scripts/install_for_ai.sh --client both
  1. If Lightroom does not already have the plugin loaded:

  • Open File -> Plug-in Manager

  • Add or enable ~/Library/Application Support/Adobe/Lightroom/Modules/LightroomMCPCustom.lrdevplugin

  1. Verify the bridge.

PYTHONPATH=src python3 scripts/smoke_bridge.py
  1. Print the MCP config snippet for your actual checkout path.

./scripts/print_mcp_config.sh
  1. Use the managed server command in your MCP client.

./scripts/start_managed_server.sh

The printed config from scripts/print_mcp_config.sh is the easiest way to avoid path mistakes. The generated command uses your current absolute checkout path and launches the managed server:

./scripts/start_managed_server.sh

That script:

  1. refreshes the plugin install

  2. checks for a live Lightroom bridge

  3. waits for the bridge port file

  4. launches the MCP server

Why The Managed Launcher Matters

The managed launcher is the reason this project behaves reliably on a normal local Lightroom setup:

  • it refreshes the plugin bundle before each run unless you opt out

  • it fails fast if Lightroom is closed, unless you explicitly enable auto-launch

  • it waits for Lightroom to publish the bridge port file

  • it only starts the MCP server after that handshake exists

That preserves the same startup model this project uses successfully in local development.

Configuration

Optional environment variables:

  • LIGHTROOM_SKIP_INSTALL=1 skips plugin reinstall

  • LIGHTROOM_AUTO_LAUNCH=1 opens Lightroom automatically when the managed server starts

  • LIGHTROOM_FORCE_RESTART=1 force restarts Lightroom before launch

  • LIGHTROOM_WAIT_SECONDS=180 changes the bridge wait timeout

  • LIGHTROOM_ROOT=/custom/path overrides the default Lightroom support directory during install

  • LIGHTROOM_WRITE_PREFS=0 skips the automatic Lightroom plugin-loader preference update

  • LIGHTROOM_VENV_DIR=/custom/venv/path overrides the Python runtime location

  • LIGHTROOM_BOOTSTRAP_PYTHON=python3.12 chooses the Python executable used for venv creation

Safety And Scope

  • Python-side validation clamps or rejects invalid Develop settings before they reach Lightroom

  • Lightroom writes happen through the Lightroom SDK, not direct .lrcat mutation

  • The bridge is localhost-only

  • This project is intentionally local-first and currently macOS-only

  • Snapshot-first is the recommended non-destructive edit pattern, but it is documentation guidance rather than a forced wrapper mode

Testing

Unit tests run with plain pytest.

Live integration tests require a running Lightroom bridge and are opt-in:

LIGHTROOM_RUN_INTEGRATION=1 pytest -q

Without that flag, the integration suite is skipped by default so CI and fresh contributors do not need Lightroom installed just to contribute.

Packaging GitHub Releases

To build release bundles:

./scripts/package_release.sh

That creates:

  • a source zip for the repo

  • a plugin zip containing LightroomMCPCustom.lrplugin

Homebrew release notes and packaging guidance live in docs/DISTRIBUTION.md.

  • Lightroom Classic only

  • macOS only

  • requires the plugin bundle to be installed locally

  • live integration tests cannot run in generic CI because they need Lightroom

Available Tools

59 tools
add_keywordsC

Add keywords (supports hierarchical format: A > B > C).

Inputs: keywords (required), local_ids. Returns: object

add_to_collectionB

Add selected photos (or specific local_ids) to a collection.

Inputs: collection_id (required), local_ids. Returns: object

apply_develop_presetC

Apply a named preset with optional parameter overrides.

Inputs: preset (required), local_ids, overrides, strict, +2 more. Returns: object

apply_develop_settingsA

Apply one or many develop settings to selected photos or local_ids.

Settings is a dict of parameter names to values, e.g.: {"Exposure": 0.5, "Contrast": 25, "SplitToningHighlightHue": 35} Use strict=True to reject unknown parameters. clamp=True (default) auto-clamps to valid ranges. history_name creates a named undo step in Lightroom.

Inputs: settings (required), local_ids, strict, clamp, +1 more. Returns: object

apply_lightroom_presetC

Apply a Lightroom develop preset by its UUID.

Inputs: preset_uuid (required), local_ids. Returns: object

apply_local_adjustment_settingsA

Set local_* adjustment parameters on the currently active mask.

Settings dict uses local_* parameter names, e.g.: {"local_Exposure": 1.5, "local_Contrast": 25, "local_Clarity": 40} A mask must be active (selected) first.

Inputs: settings (required). Returns: object

apply_snapshotC

Apply a develop snapshot by its ID.

Inputs: snapshot_id (required), local_ids. Returns: object

auto_toneB

Run Auto Tone style adjustments for selected photos or local_ids.

Inputs: local_ids. Returns: object

auto_white_balanceB

Set white balance to Auto for selected photos or local_ids.

Inputs: local_ids. Returns: object

batch_apply_develop_operationsA

Run multiple preset/settings/parameter/group operations in sequence.

Each operation is a dict with one of: preset, settings, parameter, or group. Examples: [{"preset": "portrait_clean"}, {"settings": {"Exposure": 0.3}}, {"parameter": "Contrast", "value": 20}] Operations run in order. Use stop_on_error=True to halt on first failure. default_local_ids applies to all operations unless overridden per-operation.

Inputs: operations (required), default_local_ids, strict, clamp, +1 more. Returns: object

can_undoB

Check if undo/redo operations are available.

Inputs: no named parameters. Returns: object

create_ai_maskB

Create an AI-powered mask on the active photo.

mask_type: subject, sky, background, person, object, depth, luminance, color operation: new (default), add, subtract, intersect

Inputs: mask_type (required), operation. Returns: object

create_collectionB

Create a new collection (optionally inside a collection set).

Inputs: name (required), parent_id. Returns: object

create_snapshotC

Create a named develop snapshot for the active photo.

Inputs: name (required), local_ids. Returns: object

create_virtual_copyC

Create virtual copies of selected photos or specific local_ids.

Inputs: local_ids. Returns: object

delete_collectionB

Delete a collection by its ID.

Inputs: collection_id (required). Returns: object

delete_snapshotC

Delete a develop snapshot by its ID.

Inputs: snapshot_id (required), local_ids. Returns: object

export_photosA

Export photos as JPG to a destination folder.

Exports selected photos (or specific local_ids) with all develop edits applied as sRGB JPEG files to the given folder path. Creates the folder if it doesn't exist.

Inputs: destination (required), local_ids, quality. Returns: object

find_photo_by_pathB

Find one Lightroom catalog photo by absolute file path.

Inputs: path (required). Returns: object

get_active_photoB

Get the active Lightroom photo in the current selection.

Inputs: no named parameters. Returns: object

get_active_photo_fileB

Get active photo metadata plus direct file-inspection details.

Inputs: no named parameters. Returns: object

get_develop_group_settingsC

Get only the settings for one named develop group.

Inputs: group (required), local_ids. Returns: object

get_develop_param_rangeC

Get slider range for a develop parameter (when Lightroom can report it).

Inputs: parameter (required). Returns: object

get_develop_settingsA

Get all ~175 develop settings for the active photo (or first in local_ids).

Returns a settings dict with every slider value: exposure, contrast, HSL, split toning, tone curve, sharpening, noise reduction, effects, etc. Call this before editing to understand the photo's current state.

Inputs: local_ids. Returns: object

get_local_adjustment_settingsA

Read local_* adjustment parameters on the currently active mask.

Returns values for local_Exposure, local_Contrast, local_Highlights, etc. A mask must be active (selected) first.

Inputs: no named parameters. Returns: object

get_selected_photo_filesB

List selected Lightroom photos with direct file-inspection details.

Inputs: limit. Returns: object

get_selected_photosB

List selected Lightroom photos with IDs and key metadata.

Inputs: limit. Returns: object

invert_maskB

Invert the active mask (or a specific mask by ID).

Inputs: mask_id. Returns: object

lightroom_list_commandsA

List command names currently exposed by the Lightroom plugin.

Inputs: no named parameters. Returns: object

lightroom_pingB

Verify Lightroom bridge connectivity and plugin health.

Inputs: no named parameters. Returns: object

lightroom_statusB

Get plugin status, command count, and socket state.

Inputs: no named parameters. Returns: object

list_collectionsB

List all collections and collection sets in the catalog.

Inputs: no named parameters. Returns: object

list_develop_groupsB

List grouped develop parameter sets for faster targeted edits.

Inputs: no named parameters. Returns: object

list_develop_parametersB

List develop parameters known by this Lightroom Classic install.

Inputs: no named parameters. Returns: object

list_develop_param_rangesB

Get numeric ranges for known develop parameters (where available).

Inputs: parameters. Returns: object

list_develop_presetsB

List built-in editing presets that can be applied in one call.

Inputs: no named parameters. Returns: object

list_lightroom_presetsB

List Lightroom's built-in and user-created develop presets.

Inputs: no named parameters. Returns: object

list_local_paramsB

List available local_* adjustment parameters for masks.

Inputs: no named parameters. Returns: object

list_snapshotsC

List develop snapshots for the active photo.

Inputs: local_ids. Returns: object

redoA

Redo the last undone Lightroom operation.

Inputs: no named parameters. Returns: object

remove_from_collectionC

Remove selected photos (or specific local_ids) from a collection.

Inputs: collection_id (required), local_ids. Returns: object

remove_keywordsC

Remove keywords by name/path from selected photos or local_ids.

Inputs: keywords (required), local_ids. Returns: object

reset_current_photoA

Reset all develop adjustments on the currently active photo.

Inputs: no named parameters. Returns: object

rotate_leftB

Rotate selected photos 90 degrees left.

Inputs: local_ids. Returns: object

rotate_rightB

Rotate selected photos 90 degrees right.

Inputs: local_ids. Returns: object

select_maskB

Select an existing mask by its numeric ID.

Inputs: mask_id (required). Returns: object

select_mask_toolC

Select a mask tool: brush, graduated-filter, radial-filter, or range-mask.

Inputs: tool (required). Returns: object

send_raw_lightroom_commandB

Send a raw command to the plugin (advanced/debug use).

Inputs: command (required), params. Returns: object

set_captionB

Set Lightroom caption metadata for selected photos or local_ids.

Inputs: caption (required), local_ids. Returns: object

set_develop_groupC

Apply a partial update to one develop parameter group.

Inputs: group (required), values (required), local_ids, strict, +2 more. Returns: object

set_develop_paramC

Set a single develop parameter across selected photos or local_ids.

Inputs: parameter (required), value (required), local_ids, strict, +2 more. Returns: object

set_labelB

Set Lightroom color label name (or empty string to clear).

Inputs: label (required), local_ids. Returns: object

set_lens_blur_bokehC

Set the lens blur bokeh shape.

Inputs: bokeh (required). Returns: object

set_pick_statusB

Set pick flag: -1 reject, 0 unflag, 1 pick.

Inputs: status (required), local_ids. Returns: object

set_ratingC

Set Lightroom star rating (0..5).

Inputs: rating (required), local_ids. Returns: object

set_titleB

Set Lightroom title metadata for selected photos or local_ids.

Inputs: title (required), local_ids. Returns: object

toggle_lens_blur_depth_vizB

Toggle the lens blur depth visualization overlay.

Inputs: no named parameters. Returns: object

toggle_mask_overlayB

Toggle the mask overlay visualization on/off.

Inputs: no named parameters. Returns: object

undoB

Undo the last Lightroom operation.

Inputs: no named parameters. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.3/5.0

    Scored across 59 tools

    Disambiguation4/5

    Most tools have distinct purposes, such as apply_develop_preset vs. apply_develop_settings, but some overlap exists, like list_develop_presets and list_lightroom_presets, which could cause confusion. However, descriptions clarify their differences, and the majority are well-defined for specific actions.

    Naming Consistency5/5

    Tool names consistently follow a verb_noun pattern, such as add_keywords, apply_develop_preset, and get_active_photo. There are no deviations in style, making it easy to predict and understand tool functions across the entire set.

    Tool Count2/5

    With 59 tools, the count is excessive for a single server, making it overwhelming and difficult to navigate. This many tools suggests poor scoping, as many functions could be consolidated or grouped, leading to potential confusion and inefficiency.

    Completeness5/5

    The tool set provides comprehensive coverage for Lightroom Classic operations, including photo editing, metadata management, collections, masks, and undo/redo functionality. There are no obvious gaps, and it supports full CRUD and lifecycle management for the domain.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      C
      quality
      C
      maintenance
      Enables AI agents to control Adobe Lightroom Classic develop settings, manage presets and catalogs, and perform round-trip handoffs with Adobe Photoshop for advanced retouching. Provides native socket bridging for exposure, color grading, search, ratings, keywords, collections, and export workflows.
      40
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to drive Adobe Lightroom Classic directly — searching and organizing catalogs, reading and writing the full set of develop parameters, building geometric and AI-generated masks (sky, subject, mountains, water, people), applying presets, and rendering previews, histograms, and exports. All capabilities are exposed as 167 MCP tools over a Lua plugin bridge, with write-confirmation sentinels so silently dropped edits surface as explicit errors.
      Apache 2.0