mcp-relight-harmonize 1.0.1 → 1.0.3

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.
package/dist/server.js CHANGED
@@ -7,11 +7,46 @@ const analyze_optical_1 = require("./tools/analyze_optical");
7
7
  const generate_relight_1 = require("./tools/generate_relight");
8
8
  const harmonize_1 = require("./tools/harmonize");
9
9
  const synthesize_prompt_1 = require("./tools/synthesize_prompt");
10
+ const list_cache_1 = require("./tools/list_cache");
10
11
  const presets_1 = require("./resources/presets");
12
+ // Common output envelope schema definition
13
+ const StandardEnvelopeSchema = {
14
+ type: "object",
15
+ properties: {
16
+ status: { type: "string", enum: ["success", "partial", "blocked", "failed"], description: "Execution status." },
17
+ summary: { type: "string", description: "Human-readable executive summary of the operation." },
18
+ data: { type: "object", description: "Typed domain payload." },
19
+ warnings: { type: "array", items: { type: "string" }, description: "Non-fatal warnings if applicable." },
20
+ evidence: {
21
+ type: "object",
22
+ properties: {
23
+ inputsDigest: { type: "string", description: "SHA-256 digest of input parameters." },
24
+ sources: {
25
+ type: "array",
26
+ items: {
27
+ type: "object",
28
+ properties: { label: { type: "string" }, uri: { type: "string" } },
29
+ required: ["label"],
30
+ },
31
+ },
32
+ artifacts: {
33
+ type: "array",
34
+ items: {
35
+ type: "object",
36
+ properties: { label: { type: "string" }, uri: { type: "string" } },
37
+ required: ["label"],
38
+ },
39
+ },
40
+ },
41
+ },
42
+ nextActions: { type: "array", items: { type: "string" }, description: "Actionable follow-up guidance." },
43
+ },
44
+ required: ["status", "summary", "data", "warnings", "evidence", "nextActions"],
45
+ };
11
46
  function createServer() {
12
47
  const server = new index_js_1.Server({
13
48
  name: "mcp-relight-harmonize",
14
- version: "1.0.0",
49
+ version: "1.0.3",
15
50
  }, {
16
51
  capabilities: {
17
52
  tools: {},
@@ -24,94 +59,332 @@ function createServer() {
24
59
  tools: [
25
60
  {
26
61
  name: "analyze_optical_profile",
27
- description: "Extract physical optical metrics from an image: Correlated Color Temperature (CCT in Kelvin), " +
28
- "dominant light vectors, azimuth & elevation angles, luminance dynamic range, and surface roughness.",
62
+ description: "Extract physical optical metrics from an image, including Correlated Color Temperature (CCT in Kelvin), " +
63
+ "dominant 3D lighting vector (azimuth and elevation angles), photometric luminance dynamic range, contrast zones, " +
64
+ "surface normal roughness index, and optionally decompose into 6 analytical image layers in Layers/ directory.\n\n" +
65
+ "• Purpose: Diagnostic optical extraction and 6-layer decomposition (Highlights, Shadows, Ambient Occlusion, Edges, Depth Normals, Chroma/Saturation). " +
66
+ "Unlike 'generate_relight_variations' which creates artistic relit renders, this tool extracts physical diagnostic metrics and visual analytical decomposition layers.\n" +
67
+ "• Behavior: Read-only by default; writes 6 analytical layer PNGs and Layer.md to the specified directory when 'extract_layers' is true. Unmetered local execution, zero network egress, zero external auth.\n" +
68
+ "• When to use: Use as the prerequisite first step to inspect baseline lighting conditions or generate the 6 diagnostic layers into Layers/ before relighting or prompting.\n" +
69
+ "• When NOT to use: Do NOT use if you need creative relighted styles (use 'generate_relight_variations'), if merging a cutout into a scene " +
70
+ "(use 'harmonize_composite'), or if you only need generative AI prompt synthesis (use 'synthesize_diffusion_prompt').\n" +
71
+ "• Alternatives: Use 'generate_relight_variations' for creative lighting styles, or 'synthesize_diffusion_prompt' for model prompts.",
29
72
  inputSchema: {
30
73
  type: "object",
31
74
  properties: {
32
75
  image_path: {
33
76
  type: "string",
34
- description: "Absolute or workspace-relative path to the image file.",
77
+ description: "Absolute or workspace-relative path to a local image file (.png, .jpg, or .jpeg). " +
78
+ "Must be an existing image under 50 MB.",
79
+ },
80
+ extract_layers: {
81
+ type: "boolean",
82
+ description: "If true, decomposes image into 6 analytical layers (Highlights, Shadows, Ambient Occlusion, Edges, Depth Normals, Chroma/Saturation) saved to Layers/ directory and generates Layer.md.",
83
+ },
84
+ layers_dir: {
85
+ type: "string",
86
+ description: "Optional target directory to store the 6 analytical layer images and Layer.md (defaults to 'Layers').",
87
+ },
88
+ user_intent: {
89
+ type: "string",
90
+ description: "Optional creative or corrective intent to dynamically tailor layer diagnostics, recommendations, and diffusion prompts.",
35
91
  },
36
92
  },
37
93
  required: ["image_path"],
38
94
  },
95
+ outputSchema: {
96
+ ...StandardEnvelopeSchema,
97
+ properties: {
98
+ ...StandardEnvelopeSchema.properties,
99
+ data: {
100
+ type: "object",
101
+ properties: {
102
+ imagePath: { type: "string", description: "Canonical resolved file path." },
103
+ dimensions: { type: "array", items: { type: "number" }, description: "[width, height] in pixels." },
104
+ colorTemperatureKelvin: { type: "number", description: "Correlated Color Temperature (CCT) in Kelvin." },
105
+ dominantLightDirectionVector: { type: "array", items: { type: "number" }, description: "Normalized [X, Y, Z] vector." },
106
+ lightingAngles: {
107
+ type: "object",
108
+ properties: {
109
+ azimuthDeg: { type: "number", description: "Horizontal angle (0-360°)." },
110
+ elevationDeg: { type: "number", description: "Vertical elevation angle (0-90°)." },
111
+ },
112
+ required: ["azimuthDeg", "elevationDeg"],
113
+ },
114
+ meanLuminance: { type: "number", description: "Average photometric luminance (0-255)." },
115
+ luminanceDynamics: {
116
+ type: "object",
117
+ properties: {
118
+ min: { type: "number" },
119
+ max: { type: "number" },
120
+ p5: { type: "number" },
121
+ median: { type: "number" },
122
+ p95: { type: "number" },
123
+ contrastRatio: { type: "number" },
124
+ },
125
+ },
126
+ contrastZones: {
127
+ type: "object",
128
+ properties: {
129
+ specularHighlightsPct: { type: "number" },
130
+ deepShadowsPct: { type: "number" },
131
+ midtonesPct: { type: "number" },
132
+ },
133
+ },
134
+ surfaceNormalVariation: { type: "number", description: "Roughness metric (std dev of normals)." },
135
+ opticalProfileSummary: { type: "string", description: "Executive summary sentence." },
136
+ },
137
+ required: [
138
+ "imagePath",
139
+ "dimensions",
140
+ "colorTemperatureKelvin",
141
+ "dominantLightDirectionVector",
142
+ "lightingAngles",
143
+ "meanLuminance",
144
+ "luminanceDynamics",
145
+ "contrastZones",
146
+ "surfaceNormalVariation",
147
+ "opticalProfileSummary",
148
+ ],
149
+ },
150
+ },
151
+ },
39
152
  },
40
153
  {
41
154
  name: "generate_relight_variations",
42
- description: "Generate 4 physically-grounded relit image variations (Ambient fill, Dramatic chiaroscuro, " +
43
- "Rim light halo, Mood golden-hour) and save them to the output cache directory.",
155
+ description: "Generate 4 physically-grounded relit image variations on disk (Ambient fill, Dramatic chiaroscuro, " +
156
+ "Rim light halo, Mood golden-hour) with mathematical adjustment logs detailing exposure compensation (EV stops) and color balance.\n\n" +
157
+ "• Purpose: Visual image transformation. Unlike 'analyze_optical_profile' which is read-only, this tool renders and writes concrete " +
158
+ "image files to the destination directory. Unlike 'synthesize_diffusion_prompt', it produces immediate local image files.\n" +
159
+ "• Behavior: Mutates filesystem by creating up to 4 image files in the output directory. Deterministic, unmetered local compute, " +
160
+ "no network egress, no authentication required. Re-running overwrites previous variations with the same base name.\n" +
161
+ "• When to use: Use when you need tangible image alternatives of a photo or product render with alternative lighting schemes.\n" +
162
+ "• When NOT to use: Do NOT use if you only need optical metrics (use 'analyze_optical_profile'), if blending a cutout into a background " +
163
+ "(use 'harmonize_composite'), or if you need diffusion AI text prompts (use 'synthesize_diffusion_prompt').\n" +
164
+ "• Alternatives: Use 'synthesize_diffusion_prompt' for text prompts targeting GPT Image or Nano Banana, or 'list_cached_variations' to browse existing outputs.",
44
165
  inputSchema: {
45
166
  type: "object",
46
167
  properties: {
47
168
  image_path: {
48
169
  type: "string",
49
- description: "Path to the source image.",
170
+ description: "Path to the input image (.png, .jpg, .jpeg) to relight.",
50
171
  },
51
172
  target_lighting: {
52
173
  type: "string",
53
174
  enum: ["Ambient", "Dramatic", "Rim", "Mood", "All"],
54
175
  default: "All",
55
- description: "Lighting preset: 'Ambient', 'Dramatic', 'Rim', 'Mood', or 'All'.",
176
+ description: "Lighting preset selection: 'Ambient' (+0.8 EV lifted shadows, 5500K daylight), " +
177
+ "'Dramatic' (-1.5 EV shadow crush, chiaroscuro S-curve), 'Rim' (+1.2 EV normal curvature perimeter halo), " +
178
+ "'Mood' (3200K tungsten amber shift, highlight bloom), or 'All' to generate all four simultaneously. Defaults to 'All'.",
56
179
  },
57
180
  output_dir: {
58
181
  type: "string",
59
- description: "Custom output directory. If omitted, uses default cache directory.",
182
+ default: "",
183
+ description: "Destination folder for generated variation image files. If omitted, defaults to the server's configured cache directory ('./generated_variations').",
60
184
  },
61
185
  },
62
186
  required: ["image_path"],
63
187
  },
188
+ outputSchema: {
189
+ ...StandardEnvelopeSchema,
190
+ properties: {
191
+ ...StandardEnvelopeSchema.properties,
192
+ data: {
193
+ type: "object",
194
+ properties: {
195
+ originalImage: { type: "string" },
196
+ outputDir: { type: "string" },
197
+ totalVariations: { type: "number" },
198
+ variations: {
199
+ type: "array",
200
+ items: {
201
+ type: "object",
202
+ properties: {
203
+ presetName: { type: "string" },
204
+ imagePath: { type: "string" },
205
+ fileSizeBytes: { type: "number" },
206
+ evShiftStops: { type: "number" },
207
+ targetCctKelvin: { type: "number" },
208
+ adjustmentsApplied: { type: "array", items: { type: "string" } },
209
+ },
210
+ required: ["presetName", "imagePath", "fileSizeBytes", "evShiftStops", "targetCctKelvin", "adjustmentsApplied"],
211
+ },
212
+ },
213
+ },
214
+ required: ["originalImage", "outputDir", "totalVariations", "variations"],
215
+ },
216
+ },
217
+ },
64
218
  },
65
219
  {
66
220
  name: "harmonize_composite",
67
- description: "Harmonize and composite a foreground element onto a background scene. Applies Reinhard color " +
68
- "statistics transfer, matches background color temperature, and synthesizes grounded contact shadows.",
221
+ description: "Harmonize and composite a foreground subject cutout onto a background environment scene image using Reinhard " +
222
+ "color statistics transfer in decorrelated lαβ space, background color temperature matching, and synthesized ground contact shadows.\n\n" +
223
+ "• Purpose: Two-image compositing and photometric harmonization. Distinct from 'generate_relight_variations' which operates on a single image, " +
224
+ "this tool resolves color mismatches and grounding between two separate image sources.\n" +
225
+ "• Behavior: Mutates filesystem by writing 1 composited PNG image to the output cache directory. Deterministic, local execution, " +
226
+ "no network egress, no authentication required.\n" +
227
+ "• When to use: Use when placing a cutout product, character, or object onto a new background environment scene, ensuring realistic color adaptation and floor shadows.\n" +
228
+ "• When NOT to use: Do NOT use to relight a single standalone image (use 'generate_relight_variations'), or to analyze metrics alone (use 'analyze_optical_profile').\n" +
229
+ "• Alternatives: Use 'generate_relight_variations' to alter lighting on a single image, or 'synthesize_diffusion_prompt' to craft inpainting prompts for seam blending.",
69
230
  inputSchema: {
70
231
  type: "object",
71
232
  properties: {
72
233
  foreground_path: {
73
234
  type: "string",
74
- description: "Path to the foreground subject cutout (PNG/JPG).",
235
+ description: "Path to foreground subject image. Supports transparent PNG (with alpha channel) or solid background.",
75
236
  },
76
237
  background_path: {
77
238
  type: "string",
78
- description: "Path to the background environment image.",
239
+ description: "Path to target background scene image (.png, .jpg, .jpeg).",
79
240
  },
80
241
  blend_mode: {
81
242
  type: "string",
82
243
  enum: ["seamless", "alpha"],
83
244
  default: "seamless",
84
- description: "Blending algorithm ('seamless' or 'alpha').",
245
+ description: "Blending mode: 'seamless' (applies Reinhard color transfer + contact shadow + alpha blend) " +
246
+ "or 'alpha' (standard alpha composite with contact shadow only). Defaults to 'seamless'.",
85
247
  },
86
248
  },
87
249
  required: ["foreground_path", "background_path"],
88
250
  },
251
+ outputSchema: {
252
+ ...StandardEnvelopeSchema,
253
+ properties: {
254
+ ...StandardEnvelopeSchema.properties,
255
+ data: {
256
+ type: "object",
257
+ properties: {
258
+ compositeImagePath: { type: "string", description: "Absolute path to the rendered composite file on disk." },
259
+ blendMode: { type: "string" },
260
+ foregroundPath: { type: "string" },
261
+ backgroundPath: { type: "string" },
262
+ backgroundCctKelvin: { type: "number", description: "Target background color temperature." },
263
+ luminanceScalingFactor: { type: "number" },
264
+ contactShadowApplied: { type: "boolean", description: "True if contact shadow was synthesized at base." },
265
+ details: { type: "object" },
266
+ },
267
+ required: [
268
+ "compositeImagePath",
269
+ "blendMode",
270
+ "foregroundPath",
271
+ "backgroundPath",
272
+ "backgroundCctKelvin",
273
+ "luminanceScalingFactor",
274
+ "contactShadowApplied",
275
+ ],
276
+ },
277
+ },
278
+ },
89
279
  },
90
280
  {
91
281
  name: "synthesize_diffusion_prompt",
92
- description: "Synthesize precision enhancement and relighting diffusion prompts for GPT Image (DALL-E 3 / GPT-4o) " +
93
- "and Nano Banana based on physical optical analysis of an image. Includes exact Kelvin temperature, " +
94
- "lighting angles, volumetric rays, contact shadows, and calibrated denoising parameters (0.35 - 0.45).",
282
+ description: "Synthesize precision enhancement and relighting diffusion prompts based on physical optical analysis of an image, " +
283
+ "tailored specifically for GPT Image (DALL-E 3 / GPT-4o) and Nano Banana. Outputs photorealistic prompts with physical keywords " +
284
+ "(exact Kelvin CCT, 3D light angles, volumetric dust rays, contact shadows) and calibrated denoising parameters (0.35 - 0.45).\n\n" +
285
+ "• Purpose: Generative AI prompt synthesis. Unlike 'generate_relight_variations' which creates image files locally, this tool translates " +
286
+ "optical geometry into targeted text prompts and hyperparameter sets for external diffusion generators.\n" +
287
+ "• Behavior: Completely read-only, deterministic, zero filesystem modifications, no network calls, and no authentication required.\n" +
288
+ "• When to use: Use when you want to feed photorealistic lighting directives or inpainting prompts into GPT Image or Nano Banana.\n" +
289
+ "• When NOT to use: Do NOT use if you need local image rendering without an external AI model (use 'generate_relight_variations'), " +
290
+ "or if merging cutouts locally (use 'harmonize_composite').\n" +
291
+ "• Alternatives: Use 'generate_relight_variations' for instant offline image files, or 'analyze_optical_profile' for raw numerical statistics.",
95
292
  inputSchema: {
96
293
  type: "object",
97
294
  properties: {
98
295
  image_path: {
99
296
  type: "string",
100
- description: "Path to the reference image.",
297
+ description: "Path to the local reference image (.png, .jpg, .jpeg) to extract optical geometry from.",
101
298
  },
102
299
  user_intent: {
103
300
  type: "string",
104
- description: "Creative intent (e.g. 'golden sunset', 'studio commercial').",
301
+ default: "",
302
+ description: "Optional creative context or scenario description (e.g., 'golden sunset portrait', 'cyberpunk studio product').",
105
303
  },
106
304
  target_model: {
107
305
  type: "string",
108
306
  enum: ["gpt_image", "nano_banana"],
109
307
  default: "gpt_image",
110
- description: "Target engine: 'gpt_image' (GPT Image) or 'nano_banana' (Nano Banana).",
308
+ description: "Target generative engine: 'gpt_image' (outputs natural descriptive studio directives with 85mm prime lens and physical illumination) " +
309
+ "or 'nano_banana' (outputs dense tokenized optical shaders, roughness index, raytraced bounce, and ground contact shadow). Defaults to 'gpt_image'.",
111
310
  },
112
311
  },
113
312
  required: ["image_path"],
114
313
  },
314
+ outputSchema: {
315
+ ...StandardEnvelopeSchema,
316
+ properties: {
317
+ ...StandardEnvelopeSchema.properties,
318
+ data: {
319
+ type: "object",
320
+ properties: {
321
+ targetModel: { type: "string", enum: ["GPT Image", "Nano Banana"] },
322
+ userIntent: { type: "string" },
323
+ enhancementPrompt: { type: "string", description: "Prompt for micro-surface detail and lens clarity upgrade." },
324
+ relightingPrompt: { type: "string", description: "Prompt for physical relighting with angles, CCT, and contact shadows." },
325
+ recommendedParameters: { type: "object", description: "Calibrated diffusion settings (denoising 0.35-0.45, etc.)." },
326
+ opticalKeywordsUsed: { type: "array", items: { type: "string" } },
327
+ },
328
+ required: [
329
+ "targetModel",
330
+ "userIntent",
331
+ "enhancementPrompt",
332
+ "relightingPrompt",
333
+ "recommendedParameters",
334
+ "opticalKeywordsUsed",
335
+ ],
336
+ },
337
+ },
338
+ },
339
+ },
340
+ {
341
+ name: "list_cached_variations",
342
+ description: "List and inspect all previously generated relight variation images and harmonized composite files stored in the output cache directory.\n\n" +
343
+ "• Purpose: Cache inventory and artifact retrieval. Resolves pipeline completeness by allowing callers to discover, verify, " +
344
+ "and retrieve generated files without manual filesystem traversal.\n" +
345
+ "• Behavior: Completely read-only, queries local filesystem cache directory, no network calls, no authentication required.\n" +
346
+ "• When to use: Use after running 'generate_relight_variations' or 'harmonize_composite' to verify written artifacts, inspect file sizes, " +
347
+ "and obtain exact paths for downstream analysis.\n" +
348
+ "• When NOT to use: Do NOT use to generate new images (use 'generate_relight_variations') or to analyze optical metrics (use 'analyze_optical_profile').\n" +
349
+ "• Alternatives: Use 'generate_relight_variations' to create new images, or 'harmonize_composite' to merge cutouts.",
350
+ inputSchema: {
351
+ type: "object",
352
+ properties: {
353
+ cache_dir: {
354
+ type: "string",
355
+ default: "",
356
+ description: "Optional custom directory path to inspect. If omitted, uses server's default cache directory.",
357
+ },
358
+ },
359
+ },
360
+ outputSchema: {
361
+ ...StandardEnvelopeSchema,
362
+ properties: {
363
+ ...StandardEnvelopeSchema.properties,
364
+ data: {
365
+ type: "object",
366
+ properties: {
367
+ cacheDirectory: { type: "string" },
368
+ totalFiles: { type: "number" },
369
+ files: {
370
+ type: "array",
371
+ items: {
372
+ type: "object",
373
+ properties: {
374
+ filename: { type: "string" },
375
+ path: { type: "string" },
376
+ sizeBytes: { type: "number" },
377
+ createdAt: { type: "string" },
378
+ type: { type: "string", enum: ["relight_variation", "harmonized_composite", "other"] },
379
+ },
380
+ required: ["filename", "path", "sizeBytes", "createdAt", "type"],
381
+ },
382
+ },
383
+ },
384
+ required: ["cacheDirectory", "totalFiles", "files"],
385
+ },
386
+ },
387
+ },
115
388
  },
116
389
  ],
117
390
  };
@@ -123,7 +396,10 @@ function createServer() {
123
396
  switch (name) {
124
397
  case "analyze_optical_profile": {
125
398
  const imagePath = String(args.image_path || "");
126
- envelope = (0, analyze_optical_1.analyzeOpticalProfileTool)(imagePath);
399
+ const extractLayers = Boolean(args.extract_layers);
400
+ const layersDir = args.layers_dir ? String(args.layers_dir) : undefined;
401
+ const userIntent = args.user_intent ? String(args.user_intent) : undefined;
402
+ envelope = (0, analyze_optical_1.analyzeOpticalProfileTool)(imagePath, extractLayers, layersDir, userIntent);
127
403
  break;
128
404
  }
129
405
  case "generate_relight_variations": {
@@ -147,6 +423,11 @@ function createServer() {
147
423
  envelope = (0, synthesize_prompt_1.synthesizeDiffusionPromptTool)(imagePath, userIntent, targetModel);
148
424
  break;
149
425
  }
426
+ case "list_cached_variations": {
427
+ const cacheDir = args.cache_dir ? String(args.cache_dir) : undefined;
428
+ envelope = (0, list_cache_1.listCachedVariationsTool)(cacheDir);
429
+ break;
430
+ }
150
431
  default:
151
432
  throw new Error(`Unknown tool: '${name}'`);
152
433
  }
@@ -1,2 +1,2 @@
1
1
  import { ResultEnvelope } from "../core/envelope";
2
- export declare function analyzeOpticalProfileTool(imagePath: string): ResultEnvelope;
2
+ export declare function analyzeOpticalProfileTool(imagePath: string, extractLayers?: boolean, layersDir?: string, userIntent?: string): ResultEnvelope;
@@ -8,19 +8,38 @@ const crypto_1 = __importDefault(require("crypto"));
8
8
  const envelope_1 = require("../core/envelope");
9
9
  const errors_1 = require("../core/errors");
10
10
  const optical_analyzer_1 = require("../domain/optical_analyzer");
11
- function analyzeOpticalProfileTool(imagePath) {
11
+ const layer_extractor_1 = require("../domain/layer_extractor");
12
+ function analyzeOpticalProfileTool(imagePath, extractLayers = false, layersDir, userIntent) {
12
13
  try {
13
14
  const report = (0, optical_analyzer_1.analyzeOpticalProfileImpl)(imagePath);
14
15
  const hash = crypto_1.default.createHash("sha256").update(imagePath).digest("hex").slice(0, 12);
15
- return (0, envelope_1.createEnvelope)("success", `Optical profile extracted successfully: ${report.opticalProfileSummary}`, report, {
16
+ let layerReport;
17
+ const artifacts = [];
18
+ if (extractLayers || layersDir) {
19
+ const targetDir = layersDir || "Layers";
20
+ layerReport = (0, layer_extractor_1.extractLayersImpl)(imagePath, targetDir, userIntent);
21
+ artifacts.push({ label: "Layer 1 - Highlights", uri: `file://${layerReport.layers.highlights.path}` }, { label: "Layer 2 - Shadows", uri: `file://${layerReport.layers.shadows.path}` }, { label: "Layer 3 - Ambient Occlusion", uri: `file://${layerReport.layers.ambientOcclusion.path}` }, { label: "Layer 4 - Edges", uri: `file://${layerReport.layers.edges.path}` }, { label: "Layer 5 - Depth Normals", uri: `file://${layerReport.layers.depthNormals.path}` }, { label: "Layer 6 - Chroma Saturation", uri: `file://${layerReport.layers.chromaSaturation.path}` }, { label: "Layer Markdown Report", uri: `file://${layerReport.layerMarkdownPath}` });
22
+ }
23
+ const payload = { ...report };
24
+ if (layerReport) {
25
+ payload.layersDirectory = layerReport.layersDirectory;
26
+ payload.layers = layerReport.layers;
27
+ payload.layerMarkdownPath = layerReport.layerMarkdownPath;
28
+ }
29
+ const nextActions = [
30
+ `Call 'generate_relight_variations' on '${imagePath}' to explore alternative lighting schemes.`,
31
+ `Call 'synthesize_diffusion_prompt' targeting GPT Image or Nano Banana for high-res generative inpainting.`,
32
+ ];
33
+ if (layerReport) {
34
+ nextActions.unshift(`Inspect 6 analytical layers and Layer.md, then present to user with prompt: 'ماذا تريد من تعديل؟'`);
35
+ }
36
+ return (0, envelope_1.createEnvelope)("success", `Optical profile extracted successfully: ${report.opticalProfileSummary}${layerReport ? ` (6 Analytical layers generated in '${layerReport.layersDirectory}')` : ""}`, payload, {
16
37
  evidence: {
17
38
  inputsDigest: `sha256:${hash}`,
18
39
  sources: [{ label: "Source Image", uri: `file://${report.imagePath}` }],
40
+ artifacts,
19
41
  },
20
- nextActions: [
21
- `Call 'generate_relight_variations' on '${imagePath}' to explore alternative lighting schemes.`,
22
- `Call 'synthesize_diffusion_prompt' targeting GPT Image or Nano Banana for high-res generative inpainting.`,
23
- ],
42
+ nextActions,
24
43
  });
25
44
  }
26
45
  catch (err) {
@@ -0,0 +1,9 @@
1
+ import { ResultEnvelope } from "../core/envelope";
2
+ export interface CachedItem {
3
+ filename: string;
4
+ path: string;
5
+ sizeBytes: number;
6
+ createdAt: string;
7
+ type: "relight_variation" | "harmonized_composite" | "other";
8
+ }
9
+ export declare function listCachedVariationsTool(cacheDir?: string): ResultEnvelope;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listCachedVariationsTool = listCachedVariationsTool;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const config_1 = require("../config");
11
+ const envelope_1 = require("../core/envelope");
12
+ const errors_1 = require("../core/errors");
13
+ function listCachedVariationsTool(cacheDir) {
14
+ try {
15
+ const targetDir = cacheDir ? path_1.default.resolve(cacheDir) : (0, config_1.ensureOutputDirectory)();
16
+ if (!fs_1.default.existsSync(targetDir)) {
17
+ return (0, envelope_1.createEnvelope)("success", `Output cache directory is empty or does not yet exist: '${targetDir}'`, { cacheDirectory: targetDir, totalFiles: 0, files: [] }, { nextActions: ["Call 'generate_relight_variations' or 'harmonize_composite' to produce variations."] });
18
+ }
19
+ const entries = fs_1.default.readdirSync(targetDir, { withFileTypes: true });
20
+ const files = [];
21
+ for (const entry of entries) {
22
+ if (entry.isFile()) {
23
+ const ext = path_1.default.extname(entry.name).toLowerCase();
24
+ if (config_1.config.allowedExtensions.has(ext)) {
25
+ const fullPath = path_1.default.join(targetDir, entry.name);
26
+ const stat = fs_1.default.statSync(fullPath);
27
+ let itemType = "other";
28
+ if (entry.name.includes("_relight_")) {
29
+ itemType = "relight_variation";
30
+ }
31
+ else if (entry.name.startsWith("harmonized_")) {
32
+ itemType = "harmonized_composite";
33
+ }
34
+ files.push({
35
+ filename: entry.name,
36
+ path: fullPath,
37
+ sizeBytes: stat.size,
38
+ createdAt: stat.mtime.toISOString(),
39
+ type: itemType,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ // Sort by most recent
45
+ files.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
46
+ const hash = crypto_1.default.createHash("sha256").update(targetDir).digest("hex").slice(0, 12);
47
+ return (0, envelope_1.createEnvelope)("success", `Found ${files.length} cached variation and composite artifacts in '${targetDir}'.`, { cacheDirectory: targetDir, totalFiles: files.length, files }, {
48
+ evidence: {
49
+ inputsDigest: `sha256:${hash}`,
50
+ artifacts: files.map((f) => ({ label: f.filename, uri: `file://${f.path}` })),
51
+ },
52
+ nextActions: [
53
+ "Call 'analyze_optical_profile' on any cached variation to evaluate its modified optical properties.",
54
+ "Call 'synthesize_diffusion_prompt' to craft AI inpainting prompts from a cached variation.",
55
+ ],
56
+ });
57
+ }
58
+ catch (err) {
59
+ const isAppErr = err instanceof errors_1.AppError;
60
+ const msg = err.message || "Failed to list cached variations.";
61
+ const code = isAppErr ? err.code : "INTERNAL_ERROR";
62
+ const hint = isAppErr ? err.actionableHint : "Verify directory read permissions.";
63
+ return (0, envelope_1.createEnvelope)("failed", `Listing cache failed: ${msg}`, { error_code: code, actionable_hint: hint }, {
64
+ warnings: [msg],
65
+ nextActions: [hint],
66
+ });
67
+ }
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-relight-harmonize",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Professional TypeScript Model Context Protocol (MCP) server & agent skill for optical profiling, physical relighting, composite harmonization, and diffusion prompt synthesis (GPT Image & Nano Banana).",
5
5
  "author": "MarwanDevSpace <dev@marwandevspace.io>",
6
6
  "license": "MIT",