Lightroom Classic MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Lightroom Classic MCP Apply a black and white preset and increase contrast by 10 for the current selection"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Lightroom Classic MCP

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 bothThat 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 bothIf 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 codexManual 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 claudeManual 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-launchWhy 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:
ask Lightroom for the active photo or current selection
use the returned absolute file path to inspect the original image directly
decide your edit
apply Lightroom changes through MCP
export only if you need a rendered before/after or final output
New inspection tools:
get_active_photo_fileget_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:
a Lightroom plugin bundle in
plugin/LightroomMCPCustom.lrplugina localhost socket bridge implemented inside Lightroom
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 bundlesrc/lightroom_mcp_custom/- Python MCP server and bridge clientscripts/install_plugin.sh- installs the plugin bundle into Lightroom's plugin foldersscripts/start_managed_server.sh- recommended launcher for daily usescripts/run_server.sh- starts the MCP server directlyscripts/smoke_bridge.py- verifies local bridge connectivityscripts/print_mcp_config.sh- prints an MCP config snippet for the current checkout pathscripts/package_release.sh- builds source and plugin zip bundles indist/
Install From A Release
If you downloaded a release zip instead of cloning the repo:
Unzip it anywhere on your Mac.
Run
./scripts/install_for_ai.sh --client both.
If you only want one client, use --client codex or --client claude.
Quick Start
Clone the repo anywhere on your Mac.
git clone <your-repo-url>
cd lightroom-classic-mcpRun the guided installer.
./scripts/install_for_ai.sh --client bothIf Lightroom does not already have the plugin loaded:
Open
File -> Plug-in ManagerAdd or enable
~/Library/Application Support/Adobe/Lightroom/Modules/LightroomMCPCustom.lrdevplugin
Verify the bridge.
PYTHONPATH=src python3 scripts/smoke_bridge.pyPrint the MCP config snippet for your actual checkout path.
./scripts/print_mcp_config.shUse the managed server command in your MCP client.
./scripts/start_managed_server.shRecommended MCP Client Command
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.shThat script:
refreshes the plugin install
checks for a live Lightroom bridge
waits for the bridge port file
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=1skips plugin reinstallLIGHTROOM_AUTO_LAUNCH=1opens Lightroom automatically when the managed server startsLIGHTROOM_FORCE_RESTART=1force restarts Lightroom before launchLIGHTROOM_WAIT_SECONDS=180changes the bridge wait timeoutLIGHTROOM_ROOT=/custom/pathoverrides the default Lightroom support directory during installLIGHTROOM_WRITE_PREFS=0skips the automatic Lightroom plugin-loader preference updateLIGHTROOM_VENV_DIR=/custom/venv/pathoverrides the Python runtime locationLIGHTROOM_BOOTSTRAP_PYTHON=python3.12chooses 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
.lrcatmutationThe 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 -qWithout 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.shThat 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 toolsadd_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
Scored across 59 tools
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.
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.
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.
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
Related MCP Connectors
Search photos and video moments by visual meaning, people, dates, and folders across local libraries, Apple Photos, and OneDrive. Manage collections, groups, favorites, people, and duplicate review. Synced metadata remains searchable while the desktop is offline; local and Apple Photos media access needs the owning device online with remote access enabled. OneDrive media can be accessed directly from the cloud. OAuth or Bearer API keys. Paid Cloud Access required; hosted visual search uses prepaid credits. Text-in-image search is semantic matching, not guaranteed exact OCR. Setup: https://organizewith.ai/en/guides/connect-ai-assistants
AI-cull, edit and export RAW photos locally with the imagic desktop app, from any MCP client.
Connect AI assistants to your approved computer for files, commands, browser and desktop tasks.
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables local automation of Adobe Lightroom Classic via the Model Context Protocol, supporting import, export, and non-destructive develop adjustments through an async job queue.9MIT
- AlicenseCqualityCmaintenanceEnables 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.40MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to iteratively edit raw photos in Lightroom Classic non-destructively, applying Develop adjustments, reviewing rendered results, and refining edits within guarded sessions.MIT
- AlicenseNot gradedqualityCmaintenanceEnables 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