@pired/lightroom-mcp 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/LightroomMCP.lrplugin/HandlerAI.lua +466 -0
  4. package/dist/LightroomMCP.lrplugin/HandlerAIMasks.lua +549 -0
  5. package/dist/LightroomMCP.lrplugin/HandlerCatalog.lua +256 -0
  6. package/dist/LightroomMCP.lrplugin/HandlerCollections.lua +337 -0
  7. package/dist/LightroomMCP.lrplugin/HandlerDevelop.lua +1668 -0
  8. package/dist/LightroomMCP.lrplugin/HandlerExport.lua +149 -0
  9. package/dist/LightroomMCP.lrplugin/HandlerImport.lua +108 -0
  10. package/dist/LightroomMCP.lrplugin/HandlerLocalAdjustments.lua +330 -0
  11. package/dist/LightroomMCP.lrplugin/HandlerMetadata.lua +169 -0
  12. package/dist/LightroomMCP.lrplugin/HandlerOrganization.lua +783 -0
  13. package/dist/LightroomMCP.lrplugin/HandlerPreview.lua +189 -0
  14. package/dist/LightroomMCP.lrplugin/HandlerSearch.lua +185 -0
  15. package/dist/LightroomMCP.lrplugin/HandlerSelection.lua +302 -0
  16. package/dist/LightroomMCP.lrplugin/HandlerSpots.lua +313 -0
  17. package/dist/LightroomMCP.lrplugin/HandlerWatermark.lua +59 -0
  18. package/dist/LightroomMCP.lrplugin/Info.lua +25 -0
  19. package/dist/LightroomMCP.lrplugin/JSON.lua +270 -0
  20. package/dist/LightroomMCP.lrplugin/Log.lua +92 -0
  21. package/dist/LightroomMCP.lrplugin/MenuShowStatus.lua +22 -0
  22. package/dist/LightroomMCP.lrplugin/PhotoLookup.lua +39 -0
  23. package/dist/LightroomMCP.lrplugin/PluginInfoProvider.lua +865 -0
  24. package/dist/LightroomMCP.lrplugin/PluginInit.lua +48 -0
  25. package/dist/cli.d.ts +6 -0
  26. package/dist/cli.d.ts.map +1 -0
  27. package/dist/cli.js +31 -0
  28. package/dist/cli.js.map +1 -0
  29. package/dist/configs/claude-code.mcp.json +9 -0
  30. package/dist/configs/claude-desktop.json +12 -0
  31. package/dist/configs/generic-stdio-client.json +10 -0
  32. package/dist/configs/mcp-proxy-http.json +9 -0
  33. package/dist/create-server.d.ts +12 -0
  34. package/dist/create-server.d.ts.map +1 -0
  35. package/dist/create-server.js +17 -0
  36. package/dist/create-server.js.map +1 -0
  37. package/dist/deepseek-harness/README.md +112 -0
  38. package/dist/deepseek-harness/deepseek-mcp-harness.mjs +292 -0
  39. package/dist/dispatcher.d.ts +34 -0
  40. package/dist/dispatcher.d.ts.map +1 -0
  41. package/dist/dispatcher.js +72 -0
  42. package/dist/dispatcher.js.map +1 -0
  43. package/dist/heartbeat.d.ts +17 -0
  44. package/dist/heartbeat.d.ts.map +1 -0
  45. package/dist/heartbeat.js +36 -0
  46. package/dist/heartbeat.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +255 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/install-plugin.d.ts +16 -0
  52. package/dist/install-plugin.d.ts.map +1 -0
  53. package/dist/install-plugin.js +125 -0
  54. package/dist/install-plugin.js.map +1 -0
  55. package/dist/instance-lock.d.ts +5 -0
  56. package/dist/instance-lock.d.ts.map +1 -0
  57. package/dist/instance-lock.js +77 -0
  58. package/dist/instance-lock.js.map +1 -0
  59. package/dist/list-tools-handler.d.ts +16 -0
  60. package/dist/list-tools-handler.d.ts.map +1 -0
  61. package/dist/list-tools-handler.js +50 -0
  62. package/dist/list-tools-handler.js.map +1 -0
  63. package/dist/plugin-liveness.d.ts +48 -0
  64. package/dist/plugin-liveness.d.ts.map +1 -0
  65. package/dist/plugin-liveness.js +75 -0
  66. package/dist/plugin-liveness.js.map +1 -0
  67. package/dist/plugin-socket.d.ts +32 -0
  68. package/dist/plugin-socket.d.ts.map +1 -0
  69. package/dist/plugin-socket.js +97 -0
  70. package/dist/plugin-socket.js.map +1 -0
  71. package/dist/ports.d.ts +3 -0
  72. package/dist/ports.d.ts.map +1 -0
  73. package/dist/ports.js +22 -0
  74. package/dist/ports.js.map +1 -0
  75. package/dist/token.d.ts +3 -0
  76. package/dist/token.d.ts.map +1 -0
  77. package/dist/token.js +23 -0
  78. package/dist/token.js.map +1 -0
  79. package/dist/tool-contracts.d.ts +13 -0
  80. package/dist/tool-contracts.d.ts.map +1 -0
  81. package/dist/tool-contracts.js +1422 -0
  82. package/dist/tool-contracts.js.map +1 -0
  83. package/dist/tool-handler.d.ts +23 -0
  84. package/dist/tool-handler.d.ts.map +1 -0
  85. package/dist/tool-handler.js +96 -0
  86. package/dist/tool-handler.js.map +1 -0
  87. package/dist/validate-args.d.ts +7 -0
  88. package/dist/validate-args.d.ts.map +1 -0
  89. package/dist/validate-args.js +37 -0
  90. package/dist/validate-args.js.map +1 -0
  91. package/dist/version.d.ts +2 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +4 -0
  94. package/dist/version.js.map +1 -0
  95. package/dist/wait-until.d.ts +7 -0
  96. package/dist/wait-until.d.ts.map +1 -0
  97. package/dist/wait-until.js +15 -0
  98. package/dist/wait-until.js.map +1 -0
  99. package/package.json +73 -0
@@ -0,0 +1,1422 @@
1
+ const MAX_BULK_PHOTO_IDS = 1000;
2
+ const MAX_KEYWORDS = 1000;
3
+ export const POINT_CURVE_SETTING_KEYS = [
4
+ "ToneCurvePV2012",
5
+ "ToneCurvePV2012Red",
6
+ "ToneCurvePV2012Green",
7
+ "ToneCurvePV2012Blue",
8
+ ];
9
+ export const DEVELOP_SETTING_KEYS = [
10
+ "WhiteBalance",
11
+ "Temperature",
12
+ "Tint",
13
+ "Exposure2012",
14
+ "Contrast2012",
15
+ "Highlights2012",
16
+ "Shadows2012",
17
+ "Whites2012",
18
+ "Blacks2012",
19
+ "Texture",
20
+ "Clarity2012",
21
+ "Dehaze",
22
+ "Vibrance",
23
+ "Saturation",
24
+ "SaturationAdjustmentRed",
25
+ "SaturationAdjustmentOrange",
26
+ "SaturationAdjustmentYellow",
27
+ "SaturationAdjustmentGreen",
28
+ "SaturationAdjustmentAqua",
29
+ "SaturationAdjustmentBlue",
30
+ "SaturationAdjustmentPurple",
31
+ "SaturationAdjustmentMagenta",
32
+ "HueAdjustmentRed",
33
+ "HueAdjustmentOrange",
34
+ "HueAdjustmentYellow",
35
+ "HueAdjustmentGreen",
36
+ "HueAdjustmentAqua",
37
+ "HueAdjustmentBlue",
38
+ "HueAdjustmentPurple",
39
+ "HueAdjustmentMagenta",
40
+ "LuminanceAdjustmentRed",
41
+ "LuminanceAdjustmentOrange",
42
+ "LuminanceAdjustmentYellow",
43
+ "LuminanceAdjustmentGreen",
44
+ "LuminanceAdjustmentAqua",
45
+ "LuminanceAdjustmentBlue",
46
+ "LuminanceAdjustmentPurple",
47
+ "LuminanceAdjustmentMagenta",
48
+ "ParametricShadows",
49
+ "ParametricDarks",
50
+ "ParametricLights",
51
+ "ParametricHighlights",
52
+ "ParametricShadowSplit",
53
+ "ParametricMidtoneSplit",
54
+ "ParametricHighlightSplit",
55
+ ...POINT_CURVE_SETTING_KEYS,
56
+ "ToneCurveName2012",
57
+ "ConvertToGrayscale",
58
+ "Sharpness",
59
+ "SharpenRadius",
60
+ "SharpenDetail",
61
+ "SharpenEdgeMasking",
62
+ "LuminanceSmoothing",
63
+ "LuminanceNoiseReductionDetail",
64
+ "LuminanceNoiseReductionContrast",
65
+ "ColorNoiseReduction",
66
+ "ColorNoiseReductionDetail",
67
+ "ColorNoiseReductionSmoothness",
68
+ "LensProfileEnable",
69
+ "LensManualDistortionAmount",
70
+ "PerspectiveVertical",
71
+ "PerspectiveHorizontal",
72
+ "PerspectiveRotate",
73
+ "PerspectiveScale",
74
+ "PerspectiveAspect",
75
+ "PerspectiveUpright",
76
+ "PostCropVignetteAmount",
77
+ "PostCropVignetteMidpoint",
78
+ "PostCropVignetteRoundness",
79
+ "PostCropVignetteFeather",
80
+ "PostCropVignetteStyle",
81
+ "GrainAmount",
82
+ "GrainSize",
83
+ "GrainFrequency",
84
+ "CropTop",
85
+ "CropLeft",
86
+ "CropBottom",
87
+ "CropRight",
88
+ "CropAngle",
89
+ ];
90
+ const stringArray = (description, maxItems) => ({
91
+ type: "array",
92
+ items: { type: "string" },
93
+ minItems: 1,
94
+ ...(maxItems ? { maxItems } : {}),
95
+ description,
96
+ });
97
+ /**
98
+ * Photo ids come back from the catalog as numbers (`localIdentifier`), so a
99
+ * caller piping search/selection output straight into a write tool sends
100
+ * numbers. Accept both rather than making every caller stringify.
101
+ */
102
+ const photoIdSchema = (description) => ({
103
+ oneOf: [{ type: "string", minLength: 1 }, { type: "number" }],
104
+ description,
105
+ });
106
+ const photoIdArray = (description) => ({
107
+ type: "array",
108
+ items: { oneOf: [{ type: "string", minLength: 1 }, { type: "number" }] },
109
+ minItems: 1,
110
+ maxItems: MAX_BULK_PHOTO_IDS,
111
+ description,
112
+ });
113
+ const dateStringSchema = (description) => ({
114
+ type: "string",
115
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
116
+ description,
117
+ });
118
+ const scalarDevelopSettingValueSchema = {
119
+ oneOf: [{ type: "number" }, { type: "string" }, { type: "boolean" }],
120
+ };
121
+ const POINT_CURVE_MIN_PAIRS = 2;
122
+ const POINT_CURVE_MAX_PAIRS = 32;
123
+ const evenLengthSchemas = Array.from({ length: POINT_CURVE_MAX_PAIRS - POINT_CURVE_MIN_PAIRS + 1 }, (_, index) => {
124
+ const length = (POINT_CURVE_MIN_PAIRS + index) * 2;
125
+ return { minItems: length, maxItems: length };
126
+ });
127
+ const pointCurveDevelopSettingValueSchema = {
128
+ type: "array",
129
+ items: { type: "integer", minimum: 0, maximum: 255 },
130
+ minItems: POINT_CURVE_MIN_PAIRS * 2,
131
+ maxItems: POINT_CURVE_MAX_PAIRS * 2,
132
+ anyOf: evenLengthSchemas,
133
+ description: "Flat input/output pairs for a Lightroom point curve, e.g. [0, 0, 64, 48, 192, 210, 255, 255]. Values are integers from 0 to 255 and the array holds 2 to 32 pairs, so its length is always even. Inputs must be strictly increasing and the curve must start at input 0 and end at input 255.",
134
+ };
135
+ const pointCurveSettingKeySet = new Set(POINT_CURVE_SETTING_KEYS);
136
+ const developSettingsProperties = Object.fromEntries(DEVELOP_SETTING_KEYS.map((key) => [
137
+ key,
138
+ pointCurveSettingKeySet.has(key)
139
+ ? pointCurveDevelopSettingValueSchema
140
+ : scalarDevelopSettingValueSchema,
141
+ ]));
142
+ const presetSelectorProperties = {
143
+ preset_name: { type: "string", minLength: 1, description: "Develop preset name" },
144
+ preset_uuid: { type: "string", minLength: 1, description: "Develop preset UUID (preferred)" },
145
+ preset_folder: { type: "string", minLength: 1, description: "Preset folder for disambiguation" },
146
+ preset_scope: {
147
+ type: "string",
148
+ enum: ["lightroom", "plugin"],
149
+ description: "Lightroom-visible preset or plugin-managed checkpoint",
150
+ },
151
+ };
152
+ const presetSelectorSchema = {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ properties: presetSelectorProperties,
156
+ anyOf: [{ required: ["preset_uuid"] }, { required: ["preset_name"] }],
157
+ };
158
+ /**
159
+ * Advanced findPhotos/searchDesc rules — the same { criteria, operation,
160
+ * value, value2? } shape Lightroom uses internally, shared by
161
+ * search_photos (advanced filters) and manage_view_filter (set).
162
+ */
163
+ const searchRulesArray = (description) => ({
164
+ type: "array",
165
+ minItems: 1,
166
+ maxItems: 20,
167
+ description,
168
+ items: {
169
+ type: "object",
170
+ additionalProperties: false,
171
+ properties: {
172
+ criteria: { type: "string", minLength: 1, description: "Field to test (e.g. 'keywords', 'rating', 'cameraModel', 'lens', 'isoSpeedRating', 'copyName', 'hasAdjustments', 'captureTime', 'text')" },
173
+ operation: { type: "string", minLength: 1, description: "Test (e.g. 'all', 'any', '==', '>=', 'in', 'startsWith')" },
174
+ value: {
175
+ oneOf: [{ type: "string" }, { type: "number" }],
176
+ description: "Value to compare (string or number)",
177
+ },
178
+ value2: {
179
+ oneOf: [{ type: "string" }, { type: "number" }],
180
+ description: "Second value for 'in' (range) operations",
181
+ },
182
+ },
183
+ required: ["criteria", "operation", "value"],
184
+ },
185
+ });
186
+ export const TOOL_CONTRACTS = [
187
+ {
188
+ name: "search_photos",
189
+ luaHandler: "HandlerSearch.searchPhotos",
190
+ description: "Search for photos in Lightroom catalog by criteria (paginated, default limit 100). Use the simplified filters (filename, keywords, rating, dates) or advanced `rules` for any findPhotos criterion (cameraModel, lens, isoSpeedRating, copyName, hasAdjustments...). Providing at least one filter significantly improves performance on large catalogs; with no filters at all it lists the whole catalog.",
191
+ inputSchema: {
192
+ type: "object",
193
+ additionalProperties: false,
194
+ properties: {
195
+ filename: { type: "string", description: "Search by filename (partial match)" },
196
+ keywords: stringArray("Search by keywords"),
197
+ rating: {
198
+ type: "number",
199
+ description: "Filter by star rating (0-5)",
200
+ minimum: 0,
201
+ maximum: 5,
202
+ },
203
+ start_date: dateStringSchema("Start date (YYYY-MM-DD)"),
204
+ end_date: dateStringSchema("End date (YYYY-MM-DD)"),
205
+ rules: searchRulesArray("Advanced criteria rules (same format as create_smart_collection). Intersect with the simplified filters unless combine is 'union'."),
206
+ combine: {
207
+ type: "string",
208
+ enum: ["intersect", "union"],
209
+ description: "How filters combine: 'intersect' (AND, default) or 'union' (OR)",
210
+ },
211
+ limit: { type: "number", description: "Max photos to return (default 100)", minimum: 0 },
212
+ offset: { type: "number", description: "Number of photos to skip (default 0)", minimum: 0 },
213
+ },
214
+ },
215
+ },
216
+ {
217
+ name: "get_selected_photos",
218
+ luaHandler: "HandlerSelection.getSelectedPhotos",
219
+ description: "Get currently selected photos in Lightroom (or filmstrip if no selection). Paginated, default limit 100.",
220
+ inputSchema: {
221
+ type: "object",
222
+ additionalProperties: false,
223
+ properties: {
224
+ limit: { type: "number", description: "Max photos to return (default 100)", minimum: 0 },
225
+ offset: { type: "number", description: "Number of photos to skip (default 0)", minimum: 0 },
226
+ },
227
+ },
228
+ },
229
+ {
230
+ name: "get_photo_metadata",
231
+ luaHandler: "HandlerMetadata.getPhotoMetadata",
232
+ description: "Get detailed metadata for a specific photo: EXIF, title/caption/headline, GPS (latitude/longitude/altitude), IPTC location (sublocation/city/stateProvince/country/isoCountryCode), copyright, and develop settings",
233
+ inputSchema: {
234
+ type: "object",
235
+ additionalProperties: false,
236
+ properties: {
237
+ photo_id: photoIdSchema("Photo ID or file path"),
238
+ },
239
+ required: ["photo_id"],
240
+ },
241
+ },
242
+ {
243
+ name: "list_collections",
244
+ luaHandler: "HandlerCollections.listCollections",
245
+ description: "List all collections in Lightroom catalog (paginated, default limit 100)",
246
+ inputSchema: {
247
+ type: "object",
248
+ additionalProperties: false,
249
+ properties: {
250
+ limit: { type: "number", description: "Max collections to return (default 100)", minimum: 0 },
251
+ offset: { type: "number", description: "Number of collections to skip (default 0)", minimum: 0 },
252
+ },
253
+ },
254
+ },
255
+ {
256
+ name: "create_collection",
257
+ luaHandler: "HandlerCollections.createCollection",
258
+ description: "Create a new collection",
259
+ inputSchema: {
260
+ type: "object",
261
+ additionalProperties: false,
262
+ properties: {
263
+ name: { type: "string", description: "Collection name" },
264
+ parent: { type: "string", description: "Parent collection set (optional)" },
265
+ },
266
+ required: ["name"],
267
+ },
268
+ },
269
+ {
270
+ name: "add_to_collection",
271
+ luaHandler: "HandlerCollections.addToCollection",
272
+ description: "Add photos to a collection",
273
+ inputSchema: {
274
+ type: "object",
275
+ additionalProperties: false,
276
+ properties: {
277
+ collection_name: { type: "string", description: "Collection name" },
278
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
279
+ },
280
+ required: ["collection_name", "photo_ids"],
281
+ },
282
+ },
283
+ {
284
+ name: "set_keywords",
285
+ luaHandler: "HandlerOrganization.setKeywords",
286
+ description: "Add or remove keywords from photos",
287
+ inputSchema: {
288
+ type: "object",
289
+ additionalProperties: false,
290
+ properties: {
291
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
292
+ add_keywords: stringArray("Keywords to add", MAX_KEYWORDS),
293
+ remove_keywords: stringArray("Keywords to remove", MAX_KEYWORDS),
294
+ },
295
+ required: ["photo_ids"],
296
+ },
297
+ },
298
+ {
299
+ name: "set_rating",
300
+ luaHandler: "HandlerOrganization.setRating",
301
+ description: "Set star rating for photos",
302
+ inputSchema: {
303
+ type: "object",
304
+ additionalProperties: false,
305
+ properties: {
306
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
307
+ rating: {
308
+ type: "number",
309
+ description: "Star rating (0-5)",
310
+ minimum: 0,
311
+ maximum: 5,
312
+ },
313
+ },
314
+ required: ["photo_ids", "rating"],
315
+ },
316
+ },
317
+ {
318
+ name: "import_photos",
319
+ luaHandler: "HandlerImport.importPhotos",
320
+ description: "Import photos into Lightroom catalog",
321
+ inputSchema: {
322
+ type: "object",
323
+ additionalProperties: false,
324
+ properties: {
325
+ source_path: { type: "string", description: "Path to photo or folder to import" },
326
+ collection_name: {
327
+ type: "string",
328
+ description: "Collection to add imported photos to (optional)",
329
+ },
330
+ copy_to: {
331
+ type: "string",
332
+ description: "Destination folder for copying files (optional)",
333
+ },
334
+ },
335
+ required: ["source_path"],
336
+ },
337
+ },
338
+ {
339
+ name: "export_photos",
340
+ luaHandler: "HandlerExport.exportPhotos",
341
+ description: "Export photos from Lightroom",
342
+ inputSchema: {
343
+ type: "object",
344
+ additionalProperties: false,
345
+ properties: {
346
+ photo_ids: photoIdArray("Array of photo IDs or file paths to export"),
347
+ destination: { type: "string", description: "Export destination folder" },
348
+ format: {
349
+ type: "string",
350
+ description: "Export format (jpeg, png, tiff, original)",
351
+ enum: ["jpeg", "png", "tiff", "original"],
352
+ },
353
+ quality: {
354
+ type: "number",
355
+ description: "JPEG quality (0-100)",
356
+ minimum: 0,
357
+ maximum: 100,
358
+ },
359
+ width: { type: "number", description: "Max width in pixels (optional)" },
360
+ height: { type: "number", description: "Max height in pixels (optional)" },
361
+ on_existing: {
362
+ type: "string",
363
+ description: "What to do when the destination already holds a file with that name (default rename). Lightroom never prompts.",
364
+ enum: ["rename", "overwrite", "skip"],
365
+ },
366
+ watermark: {
367
+ type: "string",
368
+ minLength: 1,
369
+ description: "Name of a Lightroom watermark preset to apply (list exact names with list_watermarks). Not valid with format 'original'.",
370
+ },
371
+ },
372
+ required: ["photo_ids", "destination"],
373
+ },
374
+ },
375
+ {
376
+ name: "list_develop_presets",
377
+ luaHandler: "HandlerDevelop.listDevelopPresets",
378
+ description: "List Lightroom-visible Develop presets and plugin-managed preset checkpoints",
379
+ inputSchema: {
380
+ type: "object",
381
+ additionalProperties: false,
382
+ properties: {},
383
+ },
384
+ },
385
+ {
386
+ name: "get_develop_preset",
387
+ luaHandler: "HandlerDevelop.getDevelopPreset",
388
+ description: "Read the settings and backing-file metadata for one exact Develop preset. Use preset_uuid or provide folder/scope when names are duplicated.",
389
+ inputSchema: presetSelectorSchema,
390
+ },
391
+ {
392
+ name: "compare_develop_presets",
393
+ luaHandler: "HandlerDevelop.compareDevelopPresets",
394
+ description: "Compare two Develop presets and return a deterministic per-setting diff for iterative style matching",
395
+ inputSchema: {
396
+ type: "object",
397
+ additionalProperties: false,
398
+ properties: {
399
+ base: { ...presetSelectorSchema, description: "Approved historical/base preset" },
400
+ candidate: { ...presetSelectorSchema, description: "Candidate preset checkpoint" },
401
+ },
402
+ required: ["base", "candidate"],
403
+ },
404
+ },
405
+ {
406
+ name: "create_develop_preset",
407
+ luaHandler: "HandlerDevelop.createDevelopPreset",
408
+ description: "Create a versioned plugin-managed Develop preset checkpoint from selected settings on one photo. The checkpoint is hidden from the Develop panel; export it for handoff.",
409
+ inputSchema: {
410
+ type: "object",
411
+ additionalProperties: false,
412
+ properties: {
413
+ photo_id: photoIdSchema("Source photo ID or file path"),
414
+ preset_name: {
415
+ type: "string",
416
+ minLength: 1,
417
+ description: "Unique versioned checkpoint name; existing plugin names are refused",
418
+ },
419
+ settings: {
420
+ type: "array",
421
+ items: { type: "string", enum: DEVELOP_SETTING_KEYS },
422
+ minItems: 1,
423
+ maxItems: DEVELOP_SETTING_KEYS.length,
424
+ uniqueItems: true,
425
+ description: "Explicit Lightroom SDK setting keys to capture from the source photo",
426
+ },
427
+ },
428
+ required: ["photo_id", "preset_name", "settings"],
429
+ },
430
+ },
431
+ {
432
+ name: "export_develop_preset",
433
+ luaHandler: "HandlerDevelop.exportDevelopPreset",
434
+ description: "Copy one exact custom or plugin-managed Develop preset backing file to a destination directory. Existing files are never overwritten; built-in presets without backing files cannot be exported.",
435
+ inputSchema: {
436
+ type: "object",
437
+ additionalProperties: false,
438
+ properties: {
439
+ ...presetSelectorProperties,
440
+ destination_dir: {
441
+ type: "string",
442
+ minLength: 1,
443
+ description: "Destination directory; created when missing",
444
+ },
445
+ filename: {
446
+ type: "string",
447
+ minLength: 1,
448
+ description: "Optional leaf filename. Extension must match the Lightroom backing file.",
449
+ },
450
+ },
451
+ required: ["destination_dir"],
452
+ anyOf: [{ required: ["preset_uuid"] }, { required: ["preset_name"] }],
453
+ },
454
+ },
455
+ {
456
+ name: "apply_develop_preset",
457
+ luaHandler: "HandlerDevelop.applyDevelopPreset",
458
+ description: "Apply one exact Develop preset to one or more photos",
459
+ inputSchema: {
460
+ type: "object",
461
+ additionalProperties: false,
462
+ properties: {
463
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
464
+ preset_name: {
465
+ type: "string",
466
+ description: "Preset name",
467
+ },
468
+ preset_uuid: { type: "string", description: "Preset UUID (preferred)" },
469
+ preset_folder: { type: "string", description: "Preset folder for disambiguation" },
470
+ preset_scope: { type: "string", enum: ["lightroom", "plugin"] },
471
+ },
472
+ required: ["photo_ids"],
473
+ anyOf: [{ required: ["preset_uuid"] }, { required: ["preset_name"] }],
474
+ },
475
+ },
476
+ {
477
+ name: "copy_develop_settings",
478
+ luaHandler: "HandlerDevelop.copyDevelopSettings",
479
+ description: "Copy Develop settings from one photo to others",
480
+ inputSchema: {
481
+ type: "object",
482
+ additionalProperties: false,
483
+ properties: {
484
+ source_id: photoIdSchema("Source photo ID or file path"),
485
+ target_ids: photoIdArray("Target photo IDs or file paths"),
486
+ settings: {
487
+ type: "array",
488
+ items: {
489
+ type: "string",
490
+ enum: DEVELOP_SETTING_KEYS,
491
+ },
492
+ minItems: 1,
493
+ maxItems: DEVELOP_SETTING_KEYS.length,
494
+ description: "Optional whitelist of SDK setting keys (e.g., Exposure2012, Contrast2012, HueAdjustmentOrange). Omit to copy all.",
495
+ },
496
+ },
497
+ required: ["source_id", "target_ids"],
498
+ },
499
+ },
500
+ {
501
+ name: "set_develop_settings",
502
+ luaHandler: "HandlerDevelop.setDevelopSettings",
503
+ description: "Set Develop settings directly on a photo. Keys use allowlisted Lightroom SDK names (Exposure2012, WhiteBalance, Contrast2012, Highlights2012, Shadows2012, Whites2012, Blacks2012, Clarity2012, Vibrance, Saturation, HueAdjustmentRed, SaturationAdjustmentOrange, LuminanceAdjustmentYellow, etc.), plus RGB composite and per-channel point curves via ToneCurvePV2012, ToneCurvePV2012Red, ToneCurvePV2012Green, and ToneCurvePV2012Blue.",
504
+ inputSchema: {
505
+ type: "object",
506
+ additionalProperties: false,
507
+ properties: {
508
+ photo_id: photoIdSchema("Photo ID or file path"),
509
+ settings: {
510
+ type: "object",
511
+ properties: developSettingsProperties,
512
+ additionalProperties: false,
513
+ minProperties: 1,
514
+ description: "Allowlisted SDK setting key/value pairs (e.g., {\"Exposure2012\": 0.5, \"SaturationAdjustmentOrange\": -10})",
515
+ },
516
+ },
517
+ required: ["photo_id", "settings"],
518
+ },
519
+ },
520
+ {
521
+ name: "ai_denoise",
522
+ luaHandler: "HandlerAI.aiDenoise",
523
+ description: "Hybrid AI noise reduction for one RAW/DNG photo. First tries Adobe's native AI Denoise (Photo > Enhance) by selecting the photo in Lightroom and replaying a configurable SendKeys sequence against the Lightroom window (Windows + Lightroom in the foreground required; defaults target the English menu). Verifies success by waiting for the new DNG that AI Denoise writes next to the original. If the native path cannot be verified, falls back to smart manual noise reduction sliders (ISO-aware) and reports which method it used via 'method' ('native' | 'manual_fallback'). Setup: keep Lightroom Classic focused, and adjust native_automation keys if your Lightroom uses another menu language.",
524
+ inputSchema: {
525
+ type: "object",
526
+ additionalProperties: false,
527
+ properties: {
528
+ photo_id: photoIdSchema("Photo ID or file path (must be RAW or DNG for the native path)"),
529
+ fallback: {
530
+ type: "string",
531
+ enum: ["manual", "none"],
532
+ description: "What to do when the native AI Denoise cannot be verified. 'manual' (default) applies smart manual noise reduction; 'none' returns an error instead.",
533
+ },
534
+ manual_settings: {
535
+ type: "object",
536
+ additionalProperties: false,
537
+ minProperties: 1,
538
+ description: "Overrides for the manual fallback sliders. Defaults are ISO-aware (luminance 35-65, color 25).",
539
+ properties: {
540
+ luminance: { type: "number", minimum: 0, maximum: 100, description: "Luminance noise reduction (0-100)" },
541
+ color: { type: "number", minimum: 0, maximum: 100, description: "Color noise reduction (0-100)" },
542
+ luminance_detail: { type: "number", minimum: 0, maximum: 100, description: "Luminance detail (0-100)" },
543
+ luminance_contrast: { type: "number", minimum: 0, maximum: 100, description: "Luminance contrast (0-100)" },
544
+ color_detail: { type: "number", minimum: 0, maximum: 100, description: "Color detail (0-100)" },
545
+ color_smoothness: { type: "number", minimum: 0, maximum: 100, description: "Color smoothness (0-100)" },
546
+ },
547
+ },
548
+ native_automation: {
549
+ type: "object",
550
+ additionalProperties: false,
551
+ description: "SendKeys automation overrides. Defaults: window_title 'Lightroom Classic', menu_keys '%pe' (Alt+P opens the Photo menu, then E picks Enhance), confirm_keys '{ENTER}', key_delay_ms 600, verify_timeout_s 90. Use arrow navigation (e.g. '%p{DOWN 3}{ENTER}') or a different first key for other menu languages.",
552
+ properties: {
553
+ window_title: { type: "string", minLength: 1, description: "Lightroom window title (partial match)" },
554
+ menu_keys: {
555
+ type: "string",
556
+ minLength: 1,
557
+ description: "SendKeys sequence that opens Photo > Enhance (e.g. '%pe')",
558
+ },
559
+ confirm_keys: {
560
+ type: "string",
561
+ minLength: 1,
562
+ description: "SendKeys sequence that confirms the Enhance dialog (default '{ENTER}')",
563
+ },
564
+ pre_delay_ms: {
565
+ type: "number",
566
+ minimum: 0,
567
+ maximum: 5000,
568
+ description: "Delay after activating the window before the first keys (default 400)",
569
+ },
570
+ key_delay_ms: {
571
+ type: "number",
572
+ minimum: 0,
573
+ maximum: 5000,
574
+ description: "Delay between the menu keys and the confirm keys (default 600)",
575
+ },
576
+ verify_timeout_s: {
577
+ type: "number",
578
+ minimum: 10,
579
+ maximum: 240,
580
+ description: "Seconds to wait for the new DNG before falling back (default 90)",
581
+ },
582
+ },
583
+ },
584
+ },
585
+ required: ["photo_id"],
586
+ },
587
+ },
588
+ {
589
+ name: "set_noise_reduction",
590
+ luaHandler: "HandlerAI.setNoiseReduction",
591
+ description: "Set manual noise reduction and sharpening sliders (SDK-native, works on any photo). Luminance = LuminanceSmoothing, color = ColorNoiseReduction, plus detail/smoothness controls and the sharpening group.",
592
+ inputSchema: {
593
+ type: "object",
594
+ additionalProperties: false,
595
+ properties: {
596
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
597
+ luminance: { type: "number", minimum: 0, maximum: 100, description: "Luminance noise reduction (0-100)" },
598
+ color: { type: "number", minimum: 0, maximum: 100, description: "Color noise reduction (0-100)" },
599
+ luminance_detail: { type: "number", minimum: 0, maximum: 100, description: "Luminance detail (0-100)" },
600
+ luminance_contrast: { type: "number", minimum: 0, maximum: 100, description: "Luminance contrast (0-100)" },
601
+ color_detail: { type: "number", minimum: 0, maximum: 100, description: "Color detail (0-100)" },
602
+ color_smoothness: { type: "number", minimum: 0, maximum: 100, description: "Color smoothness (0-100)" },
603
+ sharpness: { type: "number", minimum: 0, maximum: 150, description: "Sharpening amount (0-150)" },
604
+ sharpen_radius: { type: "number", minimum: 0.5, maximum: 3, description: "Sharpening radius (0.5-3)" },
605
+ sharpen_detail: { type: "number", minimum: 0, maximum: 100, description: "Sharpening detail (0-100)" },
606
+ sharpen_edge_masking: { type: "number", minimum: 0, maximum: 100, description: "Edge masking (0-100)" },
607
+ },
608
+ required: ["photo_ids"],
609
+ anyOf: [
610
+ { required: ["luminance"] },
611
+ { required: ["color"] },
612
+ { required: ["luminance_detail"] },
613
+ { required: ["luminance_contrast"] },
614
+ { required: ["color_detail"] },
615
+ { required: ["color_smoothness"] },
616
+ { required: ["sharpness"] },
617
+ { required: ["sharpen_radius"] },
618
+ { required: ["sharpen_detail"] },
619
+ { required: ["sharpen_edge_masking"] },
620
+ ],
621
+ },
622
+ },
623
+ {
624
+ name: "set_white_balance",
625
+ luaHandler: "HandlerDevelop.setWhiteBalance",
626
+ description: "Set white balance on photos via a preset (As Shot, Auto, Daylight, Cloudy, Shade, Tungsten, Fluorescent, Flash) or explicit Kelvin temperature (2000-50000) and/or tint (-150 green to 150 magenta). 'Auto' is computed synchronously on SDK 13+. Full effect only on RAW/DNG files.",
627
+ inputSchema: {
628
+ type: "object",
629
+ additionalProperties: false,
630
+ properties: {
631
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
632
+ preset: {
633
+ type: "string",
634
+ enum: ["As Shot", "Auto", "Daylight", "Cloudy", "Shade", "Tungsten", "Fluorescent", "Flash"],
635
+ description: "White balance preset",
636
+ },
637
+ temperature: {
638
+ type: "number",
639
+ minimum: 2000,
640
+ maximum: 50000,
641
+ description: "Custom temperature in Kelvin (implies preset 'Custom')",
642
+ },
643
+ tint: {
644
+ type: "number",
645
+ minimum: -150,
646
+ maximum: 150,
647
+ description: "Custom tint, -150 (green) to 150 (magenta)",
648
+ },
649
+ },
650
+ required: ["photo_ids"],
651
+ anyOf: [{ required: ["preset"] }, { required: ["temperature"] }, { required: ["tint"] }],
652
+ },
653
+ },
654
+ {
655
+ name: "set_flags",
656
+ luaHandler: "HandlerOrganization.setFlags",
657
+ description: "Set the pick/reject flag on photos ('pick', 'reject', or 'none' to clear). Uses the UI selection commands and verifies per photo; photos hidden from the current view source are retried individually and reported.",
658
+ inputSchema: {
659
+ type: "object",
660
+ additionalProperties: false,
661
+ properties: {
662
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
663
+ flag: {
664
+ type: "string",
665
+ enum: ["pick", "reject", "none"],
666
+ description: "Flag to set ('none' clears pick/reject)",
667
+ },
668
+ },
669
+ required: ["photo_ids", "flag"],
670
+ },
671
+ },
672
+ {
673
+ name: "get_spots",
674
+ luaHandler: "HandlerSpots.getSpots",
675
+ description: "List the spot removal (heal/clone) spots of a photo: normalized center, type, source, radius, plus the raw RetouchInfo entries.",
676
+ inputSchema: {
677
+ type: "object",
678
+ additionalProperties: false,
679
+ properties: {
680
+ photo_id: photoIdSchema("Photo ID or file path"),
681
+ },
682
+ required: ["photo_id"],
683
+ },
684
+ },
685
+ {
686
+ name: "add_spots",
687
+ luaHandler: "HandlerSpots.addSpots",
688
+ description: "Add spot removal spots (heal or clone) to a photo. Coordinates are normalized 0..1 (x right, y down; get_photo_metadata dimensions help convert pixels). The source point defaults to an offset left of the spot; Lightroom recomputes optimal sources automatically in most cases. Existing spots are preserved verbatim and the write is verified by reading back.",
689
+ inputSchema: {
690
+ type: "object",
691
+ additionalProperties: false,
692
+ properties: {
693
+ photo_id: photoIdSchema("Photo ID or file path"),
694
+ spots: {
695
+ type: "array",
696
+ minItems: 1,
697
+ maxItems: 50,
698
+ description: "Spots to add",
699
+ items: {
700
+ type: "object",
701
+ additionalProperties: false,
702
+ properties: {
703
+ x: { type: "number", minimum: 0, maximum: 1, description: "Center X (normalized 0..1)" },
704
+ y: { type: "number", minimum: 0, maximum: 1, description: "Center Y (normalized 0..1)" },
705
+ radius: { type: "number", minimum: 0.001, maximum: 1, description: "Radius (normalized; default 0.05)" },
706
+ type: { type: "string", enum: ["heal", "clone"], description: "Spot type (default 'heal')" },
707
+ source_x: { type: "number", minimum: 0, maximum: 1, description: "Source X (optional; auto when omitted)" },
708
+ source_y: { type: "number", minimum: 0, maximum: 1, description: "Source Y (optional; auto when omitted)" },
709
+ },
710
+ required: ["x", "y"],
711
+ },
712
+ },
713
+ },
714
+ required: ["photo_id", "spots"],
715
+ },
716
+ },
717
+ {
718
+ name: "clear_spots",
719
+ luaHandler: "HandlerSpots.clearSpots",
720
+ description: "Remove ALL spot removal spots from a photo (verified by reading back).",
721
+ inputSchema: {
722
+ type: "object",
723
+ additionalProperties: false,
724
+ properties: {
725
+ photo_id: photoIdSchema("Photo ID or file path"),
726
+ },
727
+ required: ["photo_id"],
728
+ },
729
+ },
730
+ {
731
+ name: "add_local_adjustment",
732
+ luaHandler: "HandlerLocalAdjustments.addLocalAdjustment",
733
+ description: "Add a local adjustment with a linear (gradient) or radial mask. Linear geometry: center_x/center_y (0..1), angle in degrees (0 = effect grows upward, 90 = to the right), span (0.02-2, default 0.6). Radial geometry: center_x/center_y, radius_x/radius_y (or radius for both, 0.01-1, default 0.3), feather (0-100, default 50), invert (mask outside the ellipse). Sliders use global Develop units: exposure in EV (-5..5), the rest -100..100. Appends to existing masks without touching them; verifies by reading back.",
734
+ inputSchema: {
735
+ type: "object",
736
+ additionalProperties: false,
737
+ properties: {
738
+ photo_id: photoIdSchema("Photo ID or file path"),
739
+ mask_type: { type: "string", enum: ["linear", "radial"], description: "Mask shape" },
740
+ center_x: { type: "number", minimum: 0, maximum: 1, description: "Center X (normalized 0..1, default 0.5)" },
741
+ center_y: { type: "number", minimum: 0, maximum: 1, description: "Center Y (normalized 0..1, default 0.5)" },
742
+ angle: { type: "number", minimum: -360, maximum: 360, description: "Linear: rotation degrees (0 = up)" },
743
+ span: { type: "number", minimum: 0.02, maximum: 2, description: "Linear: total gradient length (default 0.6)" },
744
+ radius: { type: "number", minimum: 0.01, maximum: 1, description: "Radial: radius (default 0.3)" },
745
+ radius_x: { type: "number", minimum: 0.01, maximum: 1, description: "Radial: horizontal radius" },
746
+ radius_y: { type: "number", minimum: 0.01, maximum: 1, description: "Radial: vertical radius" },
747
+ feather: { type: "number", minimum: 0, maximum: 100, description: "Radial: edge feather (default 50)" },
748
+ flow: { type: "number", minimum: 0, maximum: 1, description: "Mask opacity/flow (default 1)" },
749
+ invert: { type: "boolean", description: "Radial: invert to affect outside the ellipse" },
750
+ exposure: { type: "number", minimum: -5, maximum: 5, description: "Local exposure (EV)" },
751
+ contrast: { type: "number", minimum: -100, maximum: 100, description: "Local contrast" },
752
+ highlights: { type: "number", minimum: -100, maximum: 100, description: "Local highlights" },
753
+ shadows: { type: "number", minimum: -100, maximum: 100, description: "Local shadows" },
754
+ whites: { type: "number", minimum: -100, maximum: 100, description: "Local whites" },
755
+ blacks: { type: "number", minimum: -100, maximum: 100, description: "Local blacks" },
756
+ clarity: { type: "number", minimum: -100, maximum: 100, description: "Local clarity" },
757
+ dehaze: { type: "number", minimum: -100, maximum: 100, description: "Local dehaze" },
758
+ saturation: { type: "number", minimum: -100, maximum: 100, description: "Local saturation" },
759
+ sharpness: { type: "number", minimum: -100, maximum: 100, description: "Local sharpness" },
760
+ noise_reduction: { type: "number", minimum: -100, maximum: 100, description: "Local noise reduction" },
761
+ temperature: { type: "number", minimum: -100, maximum: 100, description: "Local temperature" },
762
+ tint: { type: "number", minimum: -100, maximum: 100, description: "Local tint" },
763
+ },
764
+ required: ["photo_id", "mask_type"],
765
+ anyOf: [
766
+ { required: ["exposure"] },
767
+ { required: ["contrast"] },
768
+ { required: ["highlights"] },
769
+ { required: ["shadows"] },
770
+ { required: ["whites"] },
771
+ { required: ["blacks"] },
772
+ { required: ["clarity"] },
773
+ { required: ["dehaze"] },
774
+ { required: ["saturation"] },
775
+ { required: ["sharpness"] },
776
+ { required: ["noise_reduction"] },
777
+ { required: ["temperature"] },
778
+ { required: ["tint"] },
779
+ ],
780
+ },
781
+ },
782
+ {
783
+ name: "read_local_adjustments",
784
+ luaHandler: "HandlerLocalAdjustments.readLocalAdjustments",
785
+ description: "Read the local adjustment masks a photo currently has (MaskGroupBasedCorrections), including AI/subject/sky masks and their exact stored structure. Use it to inspect masks before adding new ones, or to learn the exact format your Lightroom version stores.",
786
+ inputSchema: {
787
+ type: "object",
788
+ additionalProperties: false,
789
+ properties: {
790
+ photo_id: photoIdSchema("Photo ID or file path"),
791
+ },
792
+ required: ["photo_id"],
793
+ },
794
+ },
795
+ {
796
+ name: "list_watermarks",
797
+ luaHandler: "HandlerWatermark.listWatermarks",
798
+ description: "List the watermark presets defined in Lightroom (from the Watermark Presets folder). Use these names with export_photos' watermark option.",
799
+ inputSchema: {
800
+ type: "object",
801
+ additionalProperties: false,
802
+ properties: {},
803
+ },
804
+ },
805
+ {
806
+ name: "add_ai_mask",
807
+ luaHandler: "HandlerAIMasks.addAIMask",
808
+ description: "Create an AI selection mask on photos — the SDK-exposed equivalent of Select Subject / Select Sky / Select Background / Select Objects / Select People / Select Landscape — and optionally apply adjustment sliders to the new mask in one pass (e.g., subject mask with exposure +0.5 and clarity +10). Requires Lightroom Classic 12.4+ with AI masking; drives the Develop module UI, so Lightroom must be running. Per-photo errors are reported instead of failing the batch; verify visually with get_photo_preview or list_masks.",
809
+ inputSchema: {
810
+ type: "object",
811
+ additionalProperties: false,
812
+ properties: {
813
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
814
+ selection_type: {
815
+ type: "string",
816
+ enum: ["subject", "sky", "background", "objects", "people", "landscape"],
817
+ description: "AI selection target. Availability depends on the Lightroom version and photo content (people/landscape need LrC 12.4+).",
818
+ },
819
+ adjustments: {
820
+ type: "object",
821
+ additionalProperties: false,
822
+ minProperties: 1,
823
+ description: "Sliders applied to the new mask (mask-context units). Local color adjustments are not offered here — use add_local_adjustment for those.",
824
+ properties: {
825
+ exposure: { type: "number", minimum: -5, maximum: 5, description: "Local exposure (EV)" },
826
+ contrast: { type: "number", minimum: -100, maximum: 100, description: "Local contrast" },
827
+ highlights: { type: "number", minimum: -100, maximum: 100, description: "Local highlights" },
828
+ shadows: { type: "number", minimum: -100, maximum: 100, description: "Local shadows" },
829
+ whites: { type: "number", minimum: -100, maximum: 100, description: "Local whites" },
830
+ blacks: { type: "number", minimum: -100, maximum: 100, description: "Local blacks" },
831
+ texture: { type: "number", minimum: -100, maximum: 100, description: "Local texture" },
832
+ clarity: { type: "number", minimum: -100, maximum: 100, description: "Local clarity" },
833
+ dehaze: { type: "number", minimum: -100, maximum: 100, description: "Local dehaze" },
834
+ vibrance: { type: "number", minimum: -100, maximum: 100, description: "Local vibrance" },
835
+ saturation: { type: "number", minimum: -100, maximum: 100, description: "Local saturation" },
836
+ sharpness: { type: "number", minimum: 0, maximum: 150, description: "Local sharpening" },
837
+ },
838
+ },
839
+ adjustment_preset: {
840
+ type: "string",
841
+ enum: ["darken_sky", "brighten_subject", "blur_background", "enhance_landscape"],
842
+ description: "Named adjustment recipe instead of a manual adjustments object: darken_sky (exposure -0.7, highlights -30, saturation +15), brighten_subject (exposure +0.5, shadows +20, clarity +10), blur_background (sharpness -80, clarity -40), enhance_landscape (clarity +30, vibrance +25, dehaze +15).",
843
+ },
844
+ },
845
+ required: ["photo_ids", "selection_type"],
846
+ // adjustments and adjustment_preset are alternatives, not companions.
847
+ not: { required: ["adjustments", "adjustment_preset"] },
848
+ },
849
+ },
850
+ {
851
+ name: "list_masks",
852
+ luaHandler: "HandlerAIMasks.listMasks",
853
+ description: "List the masks of a photo through the Develop module (LrDevelopController.getAllMasks): count plus each mask entry verbatim. Entries' shape varies between Lightroom versions; pair with read_local_adjustments for the full stored structure (MaskGroupBasedCorrections).",
854
+ inputSchema: {
855
+ type: "object",
856
+ additionalProperties: false,
857
+ properties: {
858
+ photo_id: photoIdSchema("Photo ID or file path"),
859
+ },
860
+ required: ["photo_id"],
861
+ },
862
+ },
863
+ {
864
+ name: "remove_mask",
865
+ luaHandler: "HandlerAIMasks.removeMask",
866
+ description: "Remove one mask from a photo by its id (from list_masks / add_ai_mask's mask_id), or every mask at once with remove_all=true (requires confirm=true). Verified through the before/after mask count; a non-matching id reports verified=false instead of failing.",
867
+ inputSchema: {
868
+ type: "object",
869
+ additionalProperties: false,
870
+ properties: {
871
+ photo_id: photoIdSchema("Photo ID or file path"),
872
+ mask_id: {
873
+ oneOf: [{ type: "string", minLength: 1 }, { type: "number" }],
874
+ description: "Mask id to delete (as returned by add_ai_mask or list_masks)",
875
+ },
876
+ remove_all: {
877
+ type: "boolean",
878
+ description: "Strip every mask from the photo (Lightroom's resetMasking)",
879
+ },
880
+ confirm: {
881
+ type: "boolean",
882
+ description: "Required confirmation when remove_all=true",
883
+ },
884
+ },
885
+ required: ["photo_id"],
886
+ anyOf: [{ required: ["mask_id"] }, { required: ["remove_all"] }],
887
+ // A specific mask and remove_all are alternatives.
888
+ not: { required: ["mask_id", "remove_all"] },
889
+ },
890
+ },
891
+ {
892
+ name: "set_tone_curve",
893
+ luaHandler: "HandlerDevelop.setToneCurve",
894
+ description: "Set a point curve on a photo: the main luminance curve or an RGB channel curve. Points are [x, y] pairs in 0-255 space with strictly increasing x; missing (0,0)/(255,255) endpoints are added automatically, so a single interior point works. Presets 'linear', 'medium_contrast' and 'strong_contrast' approximate Lightroom's built-in curve dropdown entries. Verified by reading the curve back.",
895
+ inputSchema: {
896
+ type: "object",
897
+ additionalProperties: false,
898
+ properties: {
899
+ photo_id: photoIdSchema("Photo ID or file path"),
900
+ channel: {
901
+ type: "string",
902
+ enum: ["main", "red", "green", "blue"],
903
+ description: "Curve channel (default 'main')",
904
+ },
905
+ points: {
906
+ type: "array",
907
+ minItems: 1,
908
+ maxItems: 32,
909
+ description: "Curve points as [x, y] pairs (0-255, x strictly increasing; endpoints are added automatically), e.g. [[64,56],[192,202]]",
910
+ items: {
911
+ type: "array",
912
+ minItems: 2,
913
+ maxItems: 2,
914
+ items: { type: "number", minimum: 0, maximum: 255 },
915
+ },
916
+ },
917
+ preset: {
918
+ type: "string",
919
+ enum: ["linear", "medium_contrast", "strong_contrast"],
920
+ description: "Built-in curve shape (alternative to points)",
921
+ },
922
+ },
923
+ required: ["photo_id"],
924
+ anyOf: [{ required: ["points"] }, { required: ["preset"] }],
925
+ },
926
+ },
927
+ {
928
+ name: "get_tone_curve",
929
+ luaHandler: "HandlerDevelop.getToneCurve",
930
+ description: "Read a photo's tone curves: curve name plus the main/red/green/blue point curves as [x, y] point arrays (and their raw flat SDK arrays).",
931
+ inputSchema: {
932
+ type: "object",
933
+ additionalProperties: false,
934
+ properties: {
935
+ photo_id: photoIdSchema("Photo ID or file path"),
936
+ },
937
+ required: ["photo_id"],
938
+ },
939
+ },
940
+ {
941
+ name: "apply_auto",
942
+ luaHandler: "HandlerDevelop.applyAuto",
943
+ description: "Apply Lightroom's official Auto commands — Auto Tone and/or Auto White Balance (the same analysis as the Auto button in Develop) — to photos. Drives the Develop module UI, so Lightroom must be running. Reports per photo which sliders actually changed (before/after diff); 'no slider changes' means the settings were already optimal or the file type does not support Auto.",
944
+ inputSchema: {
945
+ type: "object",
946
+ additionalProperties: false,
947
+ properties: {
948
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
949
+ operations: {
950
+ type: "array",
951
+ minItems: 1,
952
+ maxItems: 2,
953
+ uniqueItems: true,
954
+ items: { type: "string", enum: ["tone", "white_balance"] },
955
+ description: "Which Auto commands to run. Defaults to both ('tone' = Auto Tone, 'white_balance' = Auto WB).",
956
+ },
957
+ },
958
+ required: ["photo_ids"],
959
+ },
960
+ },
961
+ {
962
+ name: "set_color_label",
963
+ luaHandler: "HandlerOrganization.setColorLabel",
964
+ description: "Set the color label on photos ('red', 'yellow', 'green', 'blue', 'purple', or 'none' to clear). Verified per photo by reading the label back (case-insensitive to tolerate custom label sets).",
965
+ inputSchema: {
966
+ type: "object",
967
+ additionalProperties: false,
968
+ properties: {
969
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
970
+ label: {
971
+ type: "string",
972
+ enum: ["red", "yellow", "green", "blue", "purple", "none"],
973
+ description: "Color label ('none' clears it)",
974
+ },
975
+ },
976
+ required: ["photo_ids", "label"],
977
+ },
978
+ },
979
+ {
980
+ name: "create_virtual_copies",
981
+ luaHandler: "HandlerOrganization.createVirtualCopies",
982
+ description: "Create virtual copies of photos (same file, independent develop settings — e.g., one color edit and one B&W from the same original). Returns the ids of the new copies, stacked with their sources.",
983
+ inputSchema: {
984
+ type: "object",
985
+ additionalProperties: false,
986
+ properties: {
987
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
988
+ count: {
989
+ type: "integer",
990
+ minimum: 1,
991
+ maximum: 20,
992
+ description: "Copies to create per photo (default 1)",
993
+ },
994
+ },
995
+ required: ["photo_ids"],
996
+ },
997
+ },
998
+ {
999
+ name: "create_smart_collection",
1000
+ luaHandler: "HandlerOrganization.createSmartCollection",
1001
+ description: "Create a smart collection that auto-populates from rules (the same criteria format Lightroom uses internally). Example: rules [{criteria:'keywords',operation:'all',value:'wedding'},{criteria:'rating',operation:'>=',value:'3'}] with combine 'intersect' (AND) keeps keyword AND rating matches; 'union' (OR) matches either. Common criteria: keywords, rating, filename, captureTime, copyName, cameraModel, lens, isoSpeedRating, hasAdjustments, text. Operations: all/any (containment), ==/</>/<=/>=, in (ranges, with value2), startsWith/endsWith.",
1002
+ inputSchema: {
1003
+ type: "object",
1004
+ additionalProperties: false,
1005
+ properties: {
1006
+ name: { type: "string", minLength: 1, maxLength: 255, description: "Collection name" },
1007
+ combine: {
1008
+ type: "string",
1009
+ enum: ["intersect", "union"],
1010
+ description: "How rules combine: 'intersect' (AND, default) or 'union' (OR)",
1011
+ },
1012
+ rules: {
1013
+ type: "array",
1014
+ minItems: 1,
1015
+ maxItems: 20,
1016
+ description: "Smart collection rules",
1017
+ items: {
1018
+ type: "object",
1019
+ additionalProperties: false,
1020
+ properties: {
1021
+ criteria: { type: "string", minLength: 1, description: "Field to test (e.g. 'keywords', 'rating', 'captureTime')" },
1022
+ operation: { type: "string", minLength: 1, description: "Test (e.g. 'all', 'any', '==', '>=', 'in', 'startsWith')" },
1023
+ value: {
1024
+ oneOf: [{ type: "string" }, { type: "number" }],
1025
+ description: "Value to compare (string or number)",
1026
+ },
1027
+ value2: {
1028
+ oneOf: [{ type: "string" }, { type: "number" }],
1029
+ description: "Second value for 'in' (range) operations",
1030
+ },
1031
+ },
1032
+ required: ["criteria", "operation", "value"],
1033
+ },
1034
+ },
1035
+ },
1036
+ required: ["name", "rules"],
1037
+ },
1038
+ },
1039
+ {
1040
+ name: "get_photo_preview",
1041
+ luaHandler: "HandlerPreview.getPhotoPreview",
1042
+ description: "Render a JPEG preview of a photo (with its current edits) and attach it inline as an image to the tool response — the visual feedback loop for AI edits: edit a photo, call get_photo_preview, and look at the result before batching (the 'preview gate' pattern). The file is also written to the lightroom-mcp previews folder and its path returned for clients that cannot render images. Rendering is asynchronous; photos still building standard previews can take a while.",
1043
+ inputSchema: {
1044
+ type: "object",
1045
+ additionalProperties: false,
1046
+ properties: {
1047
+ photo_id: photoIdSchema("Photo ID or file path"),
1048
+ size: {
1049
+ oneOf: [
1050
+ { type: "string", enum: ["small", "medium", "large"] },
1051
+ { type: "number", minimum: 32, maximum: 2048 },
1052
+ ],
1053
+ description: "Preview size: 'small' (240px), 'medium' (640px, default), 'large' (1024px), or an exact pixel size (32-2048)",
1054
+ },
1055
+ },
1056
+ required: ["photo_id"],
1057
+ },
1058
+ },
1059
+ {
1060
+ name: "get_develop_settings",
1061
+ luaHandler: "HandlerDevelop.getDevelopSettings",
1062
+ description: "Read a photo's develop settings (the stored values: exposure, contrast, WB, curves, ...). fields='basic' (default) returns the common develop sliders — small, LLM-friendly; fields='all' returns everything including mask structures. Read-only counterpart of set_develop_settings, and the 'before' half of before/after comparisons.",
1063
+ inputSchema: {
1064
+ type: "object",
1065
+ additionalProperties: false,
1066
+ properties: {
1067
+ photo_id: photoIdSchema("Photo ID or file path"),
1068
+ fields: {
1069
+ type: "string",
1070
+ enum: ["basic", "all"],
1071
+ description: "'basic' (default): common sliders + curves; 'all': full stored settings",
1072
+ },
1073
+ },
1074
+ required: ["photo_id"],
1075
+ },
1076
+ },
1077
+ {
1078
+ name: "reset_develop",
1079
+ luaHandler: "HandlerDevelop.resetDevelop",
1080
+ description: "Reset a photo's develop settings: scope='all' (like the Reset button), scope='tools' (reset crop/transforms/spot_removal/redeye/healing/masking/gradient/circular_gradient/brushing individually), or scope='params' (reset specific parameters by name). Drives the Develop module; reports a before/after slider diff so you can see what actually changed.",
1081
+ inputSchema: {
1082
+ type: "object",
1083
+ additionalProperties: false,
1084
+ properties: {
1085
+ photo_id: photoIdSchema("Photo ID or file path"),
1086
+ scope: {
1087
+ type: "string",
1088
+ enum: ["all", "tools", "params"],
1089
+ description: "What to reset: everything, specific tools, or specific parameters (default 'all')",
1090
+ },
1091
+ tools: {
1092
+ type: "array",
1093
+ minItems: 1,
1094
+ uniqueItems: true,
1095
+ items: {
1096
+ type: "string",
1097
+ enum: [
1098
+ "crop",
1099
+ "transforms",
1100
+ "spot_removal",
1101
+ "redeye",
1102
+ "healing",
1103
+ "masking",
1104
+ "gradient",
1105
+ "circular_gradient",
1106
+ "brushing",
1107
+ ],
1108
+ },
1109
+ description: "Tools to reset when scope='tools' ('masking' removes ALL masks)",
1110
+ },
1111
+ params: {
1112
+ type: "array",
1113
+ minItems: 1,
1114
+ maxItems: 100,
1115
+ items: { type: "string", enum: [...DEVELOP_SETTING_KEYS] },
1116
+ description: "Parameter names to reset when scope='params' (same names set_develop_settings accepts)",
1117
+ },
1118
+ },
1119
+ required: ["photo_id"],
1120
+ // tools/params arrays are required by their scope.
1121
+ allOf: [
1122
+ {
1123
+ if: { properties: { scope: { const: "tools" } }, required: ["scope"] },
1124
+ then: { required: ["tools"] },
1125
+ },
1126
+ {
1127
+ if: { properties: { scope: { const: "params" } }, required: ["scope"] },
1128
+ then: { required: ["params"] },
1129
+ },
1130
+ ],
1131
+ },
1132
+ },
1133
+ {
1134
+ name: "set_process_version",
1135
+ luaHandler: "HandlerDevelop.setProcessVersion",
1136
+ description: "Switch a photo's process version (the calibration engine): 'Version 3' = Process 2012, higher numbers are newer engines (LrC 13+ = 'Version 6'). Modernizing an old photo unlocks AI masking and modern sliders at the cost of a rendering change; going back reverts the look. Reports the version before/after as Lightroom reports it.",
1137
+ inputSchema: {
1138
+ type: "object",
1139
+ additionalProperties: false,
1140
+ properties: {
1141
+ photo_id: photoIdSchema("Photo ID or file path"),
1142
+ version: {
1143
+ type: "string",
1144
+ enum: ["Version 1", "Version 2", "Version 3", "Version 4", "Version 5", "Version 6"],
1145
+ description: "Process version to switch to",
1146
+ },
1147
+ },
1148
+ required: ["photo_id", "version"],
1149
+ },
1150
+ },
1151
+ {
1152
+ name: "create_snapshot",
1153
+ luaHandler: "HandlerDevelop.createSnapshot",
1154
+ description: "Create a named develop snapshot of a photo (an entry in Lightroom's Snapshots panel) — an undo checkpoint before risky edits, e.g., 'before AI denoise' or 'client-approved color'. Create-only: the SDK cannot list or switch snapshots back, so verify in the Snapshots panel.",
1155
+ inputSchema: {
1156
+ type: "object",
1157
+ additionalProperties: false,
1158
+ properties: {
1159
+ photo_id: photoIdSchema("Photo ID or file path"),
1160
+ name: { type: "string", minLength: 1, maxLength: 255, description: "Snapshot name" },
1161
+ },
1162
+ required: ["photo_id", "name"],
1163
+ },
1164
+ },
1165
+ {
1166
+ name: "select_photos",
1167
+ luaHandler: "HandlerSelection.selectPhotos",
1168
+ description: "Set which photos are selected in Lightroom's UI — the selection drives every LrSelection-based command and stages the photo the Develop module loads. Pass photo_ids to replace the selection, or mode: 'all', 'none', 'inverse', 'deselect_others'. Photos outside the current view source are ignored by Lightroom; the response reports how many actually got selected.",
1169
+ inputSchema: {
1170
+ type: "object",
1171
+ additionalProperties: false,
1172
+ properties: {
1173
+ photo_ids: photoIdArray("Array of photo IDs or file paths to select (first becomes active)"),
1174
+ mode: {
1175
+ type: "string",
1176
+ enum: ["all", "none", "inverse", "deselect_others"],
1177
+ description: "UI selection mode instead of explicit ids",
1178
+ },
1179
+ },
1180
+ anyOf: [{ required: ["photo_ids"] }, { required: ["mode"] }],
1181
+ // Explicit ids and a UI mode are alternatives.
1182
+ not: { required: ["photo_ids", "mode"] },
1183
+ },
1184
+ },
1185
+ {
1186
+ name: "navigate_photo",
1187
+ luaHandler: "HandlerSelection.navigatePhoto",
1188
+ description: "Move to the next or previous photo in the filmstrip (like the arrow keys); the loupe view follows. Returns the now-active photo so you can chain get_photo_preview or develop tools without an extra round-trip.",
1189
+ inputSchema: {
1190
+ type: "object",
1191
+ additionalProperties: false,
1192
+ properties: {
1193
+ direction: {
1194
+ type: "string",
1195
+ enum: ["next", "previous"],
1196
+ description: "Direction to move (default 'next')",
1197
+ },
1198
+ },
1199
+ },
1200
+ },
1201
+ {
1202
+ name: "get_photo_status",
1203
+ luaHandler: "HandlerOrganization.getPhotoStatus",
1204
+ description: "Read the review status of photos in one call: flag (pick/reject/none), star rating and color label per photo. The read-only counterpart of set_flags / set_rating / set_color_label — ideal for triage flows ('show me everything unflagged', then batch-flag).",
1205
+ inputSchema: {
1206
+ type: "object",
1207
+ additionalProperties: false,
1208
+ properties: {
1209
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
1210
+ },
1211
+ required: ["photo_ids"],
1212
+ },
1213
+ },
1214
+ {
1215
+ name: "batch_metadata",
1216
+ luaHandler: "HandlerOrganization.batchMetadata",
1217
+ description: "Set IPTC text metadata (title, caption, headline, location, city, stateProvince, country, isoCountryCode, creator, copyright) on many photos at once; a null value clears the field. Verified per photo by reading every field back.",
1218
+ inputSchema: {
1219
+ type: "object",
1220
+ additionalProperties: false,
1221
+ properties: {
1222
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
1223
+ metadata: {
1224
+ type: "object",
1225
+ additionalProperties: false,
1226
+ minProperties: 1,
1227
+ description: "Fields to write (null clears a field)",
1228
+ properties: {
1229
+ title: { type: "string", description: "IPTC title" },
1230
+ caption: { type: "string", description: "IPTC caption / description" },
1231
+ headline: { type: "string", description: "IPTC headline" },
1232
+ location: { type: "string", description: "IPTC location (sublocation)" },
1233
+ city: { type: "string", description: "IPTC city" },
1234
+ stateProvince: { type: "string", description: "IPTC state/province" },
1235
+ country: { type: "string", description: "IPTC country (readable name)" },
1236
+ isoCountryCode: { type: "string", description: "IPTC country code (e.g. 'AR')" },
1237
+ creator: { type: "string", description: "IPTC creator / photographer" },
1238
+ copyright: { type: "string", description: "IPTC copyright notice" },
1239
+ },
1240
+ },
1241
+ },
1242
+ required: ["photo_ids", "metadata"],
1243
+ },
1244
+ },
1245
+ {
1246
+ name: "rotate_photo",
1247
+ luaHandler: "HandlerOrganization.rotatePhoto",
1248
+ description: "Rotate photos 90 degrees left or right (batch). Applies to the stored orientation, so previews and exports pick it up.",
1249
+ inputSchema: {
1250
+ type: "object",
1251
+ additionalProperties: false,
1252
+ properties: {
1253
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
1254
+ direction: {
1255
+ type: "string",
1256
+ enum: ["left", "right"],
1257
+ description: "Rotation direction (default 'right' — clockwise)",
1258
+ },
1259
+ },
1260
+ required: ["photo_ids"],
1261
+ },
1262
+ },
1263
+ {
1264
+ name: "remove_from_catalog",
1265
+ luaHandler: "HandlerOrganization.removeFromCatalog",
1266
+ description: "Remove photos from the Lightroom catalog. DESTRUCTIVE (undo is limited) but files on disk are NOT deleted — only the catalog entries. Requires confirm=true; the handler verifies the ids no longer resolve afterwards.",
1267
+ inputSchema: {
1268
+ type: "object",
1269
+ additionalProperties: false,
1270
+ properties: {
1271
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
1272
+ confirm: {
1273
+ type: "boolean",
1274
+ enum: [true],
1275
+ description: "Must be true — guards against accidental catalog removal",
1276
+ },
1277
+ },
1278
+ required: ["photo_ids", "confirm"],
1279
+ },
1280
+ },
1281
+ {
1282
+ name: "list_folders",
1283
+ luaHandler: "HandlerCatalog.listFolders",
1284
+ description: "List the catalog's folder tree (the Folders panel): each root folder with its name, path and photo counts; set include_subfolders for the full tree. Folders are identified by path (they have no numeric id) — use the paths with search_photos rules or when importing.",
1285
+ inputSchema: {
1286
+ type: "object",
1287
+ additionalProperties: false,
1288
+ properties: {
1289
+ include_subfolders: {
1290
+ type: "boolean",
1291
+ description: "Also list subfolders recursively (default false: only root folders)",
1292
+ },
1293
+ },
1294
+ },
1295
+ },
1296
+ {
1297
+ name: "list_keywords",
1298
+ luaHandler: "HandlerCatalog.listKeywords",
1299
+ description: "List the catalog's top-level keyword tree entries with their photo counts — the vocabulary set_keywords can draw from (use set_keywords with add_keywords to attach them to photos).",
1300
+ inputSchema: {
1301
+ type: "object",
1302
+ additionalProperties: false,
1303
+ properties: {},
1304
+ },
1305
+ },
1306
+ {
1307
+ name: "manage_view_filter",
1308
+ luaHandler: "HandlerCatalog.manageViewFilter",
1309
+ description: "Read, set or clear Lightroom's Library view filter (the filter bar above the grid). Set uses the same rule format as create_smart_collection (e.g., rating >= 3 AND keyword 'wedding'). The filter changes what the grid shows — combine with get_selected_photos / select_photos mode 'all' to operate on the filtered set.",
1310
+ inputSchema: {
1311
+ type: "object",
1312
+ additionalProperties: false,
1313
+ properties: {
1314
+ action: {
1315
+ type: "string",
1316
+ enum: ["get", "set", "clear"],
1317
+ description: "Read the current filter, apply rules, or clear it (default 'get')",
1318
+ },
1319
+ combine: {
1320
+ type: "string",
1321
+ enum: ["intersect", "union"],
1322
+ description: "How rules combine: 'intersect' (AND, default) or 'union' (OR)",
1323
+ },
1324
+ rules: searchRulesArray("Filter rules (required when action='set')"),
1325
+ },
1326
+ // rules are only mandatory for the 'set' action.
1327
+ allOf: [
1328
+ {
1329
+ if: { properties: { action: { const: "set" } }, required: ["action"] },
1330
+ then: { required: ["rules"] },
1331
+ },
1332
+ ],
1333
+ },
1334
+ },
1335
+ {
1336
+ name: "get_collection_photos",
1337
+ luaHandler: "HandlerCollections.getCollectionPhotos",
1338
+ description: "List the photos inside a collection (by name, including inside collection sets), paginated, with id/path/filename/rating/label per photo — the bridge from a collection to the tools that work on photo ids.",
1339
+ inputSchema: {
1340
+ type: "object",
1341
+ additionalProperties: false,
1342
+ properties: {
1343
+ collection_name: { type: "string", minLength: 1, description: "Collection name (as shown by list_collections)" },
1344
+ limit: { type: "number", description: "Max photos to return (default 100)", minimum: 0 },
1345
+ offset: { type: "number", description: "Number of photos to skip (default 0)", minimum: 0 },
1346
+ },
1347
+ required: ["collection_name"],
1348
+ },
1349
+ },
1350
+ {
1351
+ name: "create_collection_set",
1352
+ luaHandler: "HandlerCollections.createCollectionSet",
1353
+ description: "Create a collection set (a folder that groups collections in the Catalog panel). Optional parent_set_name nests it inside another set.",
1354
+ inputSchema: {
1355
+ type: "object",
1356
+ additionalProperties: false,
1357
+ properties: {
1358
+ name: { type: "string", minLength: 1, maxLength: 255, description: "Set name" },
1359
+ parent_set_name: { type: "string", minLength: 1, description: "Parent set to nest inside (optional)" },
1360
+ },
1361
+ required: ["name"],
1362
+ },
1363
+ },
1364
+ {
1365
+ name: "add_range_mask",
1366
+ luaHandler: "HandlerAIMasks.addRangeMask",
1367
+ description: "Create a range mask (luminance / color / depth) on photos and optionally apply adjustment sliders to it — e.g., a luminance mask with exposure -0.5 darkens the bright tones. HONEST LIMITATION: the SDK cannot set the range bounds or sample points, so the mask starts covering the full range; refine the bounds in Lightroom's UI, or drive the look through the sliders. 'depth' needs photos with depth data (e.g., iPhone portraits).",
1368
+ inputSchema: {
1369
+ type: "object",
1370
+ additionalProperties: false,
1371
+ properties: {
1372
+ photo_ids: photoIdArray("Array of photo IDs or file paths"),
1373
+ range_type: {
1374
+ type: "string",
1375
+ enum: ["luminance", "color", "depth"],
1376
+ description: "What the mask selects by (default 'luminance')",
1377
+ },
1378
+ adjustments: {
1379
+ type: "object",
1380
+ additionalProperties: false,
1381
+ minProperties: 1,
1382
+ description: "Sliders applied to the new mask (mask-context units)",
1383
+ properties: {
1384
+ exposure: { type: "number", minimum: -5, maximum: 5, description: "Local exposure (EV)" },
1385
+ contrast: { type: "number", minimum: -100, maximum: 100, description: "Local contrast" },
1386
+ highlights: { type: "number", minimum: -100, maximum: 100, description: "Local highlights" },
1387
+ shadows: { type: "number", minimum: -100, maximum: 100, description: "Local shadows" },
1388
+ whites: { type: "number", minimum: -100, maximum: 100, description: "Local whites" },
1389
+ blacks: { type: "number", minimum: -100, maximum: 100, description: "Local blacks" },
1390
+ texture: { type: "number", minimum: -100, maximum: 100, description: "Local texture" },
1391
+ clarity: { type: "number", minimum: -100, maximum: 100, description: "Local clarity" },
1392
+ dehaze: { type: "number", minimum: -100, maximum: 100, description: "Local dehaze" },
1393
+ vibrance: { type: "number", minimum: -100, maximum: 100, description: "Local vibrance" },
1394
+ saturation: { type: "number", minimum: -100, maximum: 100, description: "Local saturation" },
1395
+ sharpness: { type: "number", minimum: 0, maximum: 150, description: "Local sharpening" },
1396
+ },
1397
+ },
1398
+ adjustment_preset: {
1399
+ type: "string",
1400
+ enum: ["darken_sky", "brighten_subject", "blur_background", "enhance_landscape"],
1401
+ description: "Named adjustment recipe instead of a manual adjustments object",
1402
+ },
1403
+ },
1404
+ required: ["photo_ids"],
1405
+ not: { required: ["adjustments", "adjustment_preset"] },
1406
+ },
1407
+ },
1408
+ {
1409
+ name: "toggle_mask_overlay",
1410
+ luaHandler: "HandlerAIMasks.toggleMaskOverlay",
1411
+ description: "Toggle the red mask overlay in Lightroom's Develop view for a photo — the fastest way for a human to eyeball what add_ai_mask / add_local_adjustment actually selected. Also useful after AI masking to review the selection edges before batching.",
1412
+ inputSchema: {
1413
+ type: "object",
1414
+ additionalProperties: false,
1415
+ properties: {
1416
+ photo_id: photoIdSchema("Photo ID or file path"),
1417
+ },
1418
+ required: ["photo_id"],
1419
+ },
1420
+ },
1421
+ ];
1422
+ //# sourceMappingURL=tool-contracts.js.map