mcp-relight-harmonize 1.0.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.
- package/.agents/skills/mcp-relight-harmonize/SKILL.md +81 -0
- package/.agents/skills/mcp-relight-harmonize/references/diffusion_guide.md +58 -0
- package/.agents/skills/mcp-relight-harmonize/references/optical_math.md +104 -0
- package/.agents/skills/mcp-relight-harmonize/references/tool_orchestration.md +66 -0
- package/.agents/skills/mcp-relight-harmonize/scripts/run_pipeline.js +83 -0
- package/MASTER.md +121 -0
- package/README.md +98 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +21 -0
- package/dist/contracts/schemas.d.ts +47 -0
- package/dist/contracts/schemas.js +37 -0
- package/dist/contracts/types.d.ts +64 -0
- package/dist/contracts/types.js +2 -0
- package/dist/core/envelope.d.ts +29 -0
- package/dist/core/envelope.js +17 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/errors.js +38 -0
- package/dist/core/security.d.ts +2 -0
- package/dist/core/security.js +37 -0
- package/dist/domain/harmonizer.d.ts +5 -0
- package/dist/domain/harmonizer.js +214 -0
- package/dist/domain/image_io.d.ts +8 -0
- package/dist/domain/image_io.js +88 -0
- package/dist/domain/optical_analyzer.d.ts +9 -0
- package/dist/domain/optical_analyzer.js +190 -0
- package/dist/domain/prompt_synthesizer.d.ts +2 -0
- package/dist/domain/prompt_synthesizer.js +98 -0
- package/dist/domain/relighter.d.ts +19 -0
- package/dist/domain/relighter.js +213 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +26 -0
- package/dist/resources/presets.d.ts +13 -0
- package/dist/resources/presets.js +45 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +191 -0
- package/dist/tools/analyze_optical.d.ts +2 -0
- package/dist/tools/analyze_optical.js +36 -0
- package/dist/tools/generate_relight.d.ts +2 -0
- package/dist/tools/generate_relight.js +42 -0
- package/dist/tools/harmonize.d.ts +2 -0
- package/dist/tools/harmonize.js +49 -0
- package/dist/tools/synthesize_prompt.d.ts +2 -0
- package/dist/tools/synthesize_prompt.js +40 -0
- package/docs/architecture.md +127 -0
- package/docs/operations.md +27 -0
- package/docs/security.md +17 -0
- package/mcp_config.json +13 -0
- package/package.json +62 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createServer = createServer;
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
6
|
+
const analyze_optical_1 = require("./tools/analyze_optical");
|
|
7
|
+
const generate_relight_1 = require("./tools/generate_relight");
|
|
8
|
+
const harmonize_1 = require("./tools/harmonize");
|
|
9
|
+
const synthesize_prompt_1 = require("./tools/synthesize_prompt");
|
|
10
|
+
const presets_1 = require("./resources/presets");
|
|
11
|
+
function createServer() {
|
|
12
|
+
const server = new index_js_1.Server({
|
|
13
|
+
name: "mcp-relight-harmonize",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
}, {
|
|
16
|
+
capabilities: {
|
|
17
|
+
tools: {},
|
|
18
|
+
resources: {},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
// List Tools Handler
|
|
22
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
23
|
+
return {
|
|
24
|
+
tools: [
|
|
25
|
+
{
|
|
26
|
+
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.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
image_path: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Absolute or workspace-relative path to the image file.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["image_path"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
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.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
image_path: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Path to the source image.",
|
|
50
|
+
},
|
|
51
|
+
target_lighting: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["Ambient", "Dramatic", "Rim", "Mood", "All"],
|
|
54
|
+
default: "All",
|
|
55
|
+
description: "Lighting preset: 'Ambient', 'Dramatic', 'Rim', 'Mood', or 'All'.",
|
|
56
|
+
},
|
|
57
|
+
output_dir: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Custom output directory. If omitted, uses default cache directory.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["image_path"],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
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.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
foreground_path: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Path to the foreground subject cutout (PNG/JPG).",
|
|
75
|
+
},
|
|
76
|
+
background_path: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Path to the background environment image.",
|
|
79
|
+
},
|
|
80
|
+
blend_mode: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: ["seamless", "alpha"],
|
|
83
|
+
default: "seamless",
|
|
84
|
+
description: "Blending algorithm ('seamless' or 'alpha').",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["foreground_path", "background_path"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
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).",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
image_path: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Path to the reference image.",
|
|
101
|
+
},
|
|
102
|
+
user_intent: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Creative intent (e.g. 'golden sunset', 'studio commercial').",
|
|
105
|
+
},
|
|
106
|
+
target_model: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["gpt_image", "nano_banana"],
|
|
109
|
+
default: "gpt_image",
|
|
110
|
+
description: "Target engine: 'gpt_image' (GPT Image) or 'nano_banana' (Nano Banana).",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["image_path"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
// Call Tool Handler
|
|
120
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
121
|
+
const { name, arguments: args = {} } = request.params;
|
|
122
|
+
let envelope;
|
|
123
|
+
switch (name) {
|
|
124
|
+
case "analyze_optical_profile": {
|
|
125
|
+
const imagePath = String(args.image_path || "");
|
|
126
|
+
envelope = (0, analyze_optical_1.analyzeOpticalProfileTool)(imagePath);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "generate_relight_variations": {
|
|
130
|
+
const imagePath = String(args.image_path || "");
|
|
131
|
+
const targetLighting = String(args.target_lighting || "All");
|
|
132
|
+
const outputDir = String(args.output_dir || "");
|
|
133
|
+
envelope = (0, generate_relight_1.generateRelightVariationsTool)(imagePath, targetLighting, outputDir);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "harmonize_composite": {
|
|
137
|
+
const fgPath = String(args.foreground_path || "");
|
|
138
|
+
const bgPath = String(args.background_path || "");
|
|
139
|
+
const blendMode = String(args.blend_mode || "seamless");
|
|
140
|
+
envelope = (0, harmonize_1.harmonizeCompositeTool)(fgPath, bgPath, blendMode);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "synthesize_diffusion_prompt": {
|
|
144
|
+
const imagePath = String(args.image_path || "");
|
|
145
|
+
const userIntent = String(args.user_intent || "");
|
|
146
|
+
const targetModel = String(args.target_model || "gpt_image");
|
|
147
|
+
envelope = (0, synthesize_prompt_1.synthesizeDiffusionPromptTool)(imagePath, userIntent, targetModel);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
default:
|
|
151
|
+
throw new Error(`Unknown tool: '${name}'`);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: JSON.stringify(envelope, null, 2),
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
// List Resources Handler
|
|
163
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
164
|
+
return {
|
|
165
|
+
resources: [
|
|
166
|
+
{
|
|
167
|
+
uri: "optical://presets",
|
|
168
|
+
name: "Lighting Presets Specification",
|
|
169
|
+
description: "Physical reference definitions for Ambient, Dramatic, Rim, and Mood lighting presets.",
|
|
170
|
+
mimeType: "application/json",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
// Read Resource Handler
|
|
176
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
|
|
177
|
+
if (request.params.uri === "optical://presets") {
|
|
178
|
+
return {
|
|
179
|
+
contents: [
|
|
180
|
+
{
|
|
181
|
+
uri: "optical://presets",
|
|
182
|
+
mimeType: "application/json",
|
|
183
|
+
text: (0, presets_1.getPresetsJson)(),
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`Resource not found: '${request.params.uri}'`);
|
|
189
|
+
});
|
|
190
|
+
return server;
|
|
191
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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.analyzeOpticalProfileTool = analyzeOpticalProfileTool;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const envelope_1 = require("../core/envelope");
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
10
|
+
const optical_analyzer_1 = require("../domain/optical_analyzer");
|
|
11
|
+
function analyzeOpticalProfileTool(imagePath) {
|
|
12
|
+
try {
|
|
13
|
+
const report = (0, optical_analyzer_1.analyzeOpticalProfileImpl)(imagePath);
|
|
14
|
+
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
|
+
evidence: {
|
|
17
|
+
inputsDigest: `sha256:${hash}`,
|
|
18
|
+
sources: [{ label: "Source Image", uri: `file://${report.imagePath}` }],
|
|
19
|
+
},
|
|
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
|
+
],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const isAppErr = err instanceof errors_1.AppError;
|
|
28
|
+
const msg = err.message || "Optical profiling failed.";
|
|
29
|
+
const code = isAppErr ? err.code : "INTERNAL_ERROR";
|
|
30
|
+
const hint = isAppErr ? err.actionableHint : "Verify image file accessibility.";
|
|
31
|
+
return (0, envelope_1.createEnvelope)("failed", `Optical profiling failed: ${msg}`, { error_code: code, actionable_hint: hint }, {
|
|
32
|
+
warnings: [msg],
|
|
33
|
+
nextActions: [hint],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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.generateRelightVariationsTool = generateRelightVariationsTool;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const envelope_1 = require("../core/envelope");
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
10
|
+
const relighter_1 = require("../domain/relighter");
|
|
11
|
+
function generateRelightVariationsTool(imagePath, targetLighting = "All", outputDir = "") {
|
|
12
|
+
try {
|
|
13
|
+
const result = (0, relighter_1.generateRelightVariationsImpl)(imagePath, targetLighting, outputDir);
|
|
14
|
+
const hash = crypto_1.default.createHash("sha256").update(imagePath).digest("hex").slice(0, 12);
|
|
15
|
+
const artifacts = result.variations.map((v) => ({
|
|
16
|
+
label: `${v.presetName} Variation`,
|
|
17
|
+
uri: `file://${v.imagePath}`,
|
|
18
|
+
}));
|
|
19
|
+
const presetNames = result.variations.map((v) => v.presetName).join(", ");
|
|
20
|
+
return (0, envelope_1.createEnvelope)("success", `Generated ${result.totalVariations} relit variations (${presetNames}) in '${result.outputDir}'.`, result, {
|
|
21
|
+
evidence: {
|
|
22
|
+
inputsDigest: `sha256:${hash}`,
|
|
23
|
+
sources: [{ label: "Input Image", uri: `file://${result.originalImage}` }],
|
|
24
|
+
artifacts,
|
|
25
|
+
},
|
|
26
|
+
nextActions: [
|
|
27
|
+
"Review generated variation files to inspect aesthetic illumination.",
|
|
28
|
+
"Use 'synthesize_diffusion_prompt' for GPT Image or Nano Banana generative enhancement.",
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const isAppErr = err instanceof errors_1.AppError;
|
|
34
|
+
const msg = err.message || "Variation generation failed.";
|
|
35
|
+
const code = isAppErr ? err.code : "INTERNAL_ERROR";
|
|
36
|
+
const hint = isAppErr ? err.actionableHint : "Check output directory permissions and input image.";
|
|
37
|
+
return (0, envelope_1.createEnvelope)("failed", `Variation generation failed: ${msg}`, { error_code: code, actionable_hint: hint }, {
|
|
38
|
+
warnings: [msg],
|
|
39
|
+
nextActions: [hint],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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.harmonizeCompositeTool = harmonizeCompositeTool;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const envelope_1 = require("../core/envelope");
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
10
|
+
const harmonizer_1 = require("../domain/harmonizer");
|
|
11
|
+
function harmonizeCompositeTool(foregroundPath, backgroundPath, blendMode = "seamless") {
|
|
12
|
+
try {
|
|
13
|
+
const result = (0, harmonizer_1.harmonizeCompositeImpl)(foregroundPath, backgroundPath, blendMode);
|
|
14
|
+
const hash = crypto_1.default
|
|
15
|
+
.createHash("sha256")
|
|
16
|
+
.update(`${foregroundPath}:${backgroundPath}:${blendMode}`)
|
|
17
|
+
.digest("hex")
|
|
18
|
+
.slice(0, 12);
|
|
19
|
+
return (0, envelope_1.createEnvelope)("success", `Harmonized composite created with matched background color temperature (${result.backgroundCctKelvin}K) and contact shadow.`, result, {
|
|
20
|
+
evidence: {
|
|
21
|
+
inputsDigest: `sha256:${hash}`,
|
|
22
|
+
sources: [
|
|
23
|
+
{ label: "Foreground Cutout", uri: `file://${result.foregroundPath}` },
|
|
24
|
+
{ label: "Background Scene", uri: `file://${result.backgroundPath}` },
|
|
25
|
+
],
|
|
26
|
+
artifacts: [
|
|
27
|
+
{
|
|
28
|
+
label: "Harmonized Composite",
|
|
29
|
+
uri: `file://${result.compositeImagePath}`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
nextActions: [
|
|
34
|
+
`Inspect the composite output at '${result.compositeImagePath}'.`,
|
|
35
|
+
"Call 'synthesize_diffusion_prompt' targeting GPT Image or Nano Banana for seamless boundary inpainting.",
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const isAppErr = err instanceof errors_1.AppError;
|
|
41
|
+
const msg = err.message || "Harmonization failed.";
|
|
42
|
+
const code = isAppErr ? err.code : "INTERNAL_ERROR";
|
|
43
|
+
const hint = isAppErr ? err.actionableHint : "Ensure foreground and background images are valid files.";
|
|
44
|
+
return (0, envelope_1.createEnvelope)("failed", `Harmonization failed: ${msg}`, { error_code: code, actionable_hint: hint }, {
|
|
45
|
+
warnings: [msg],
|
|
46
|
+
nextActions: [hint],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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.synthesizeDiffusionPromptTool = synthesizeDiffusionPromptTool;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const envelope_1 = require("../core/envelope");
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
10
|
+
const prompt_synthesizer_1 = require("../domain/prompt_synthesizer");
|
|
11
|
+
function synthesizeDiffusionPromptTool(imagePath, userIntent = "", targetModel = "gpt_image") {
|
|
12
|
+
try {
|
|
13
|
+
const result = (0, prompt_synthesizer_1.synthesizeDiffusionPromptImpl)(imagePath, userIntent, targetModel);
|
|
14
|
+
const hash = crypto_1.default
|
|
15
|
+
.createHash("sha256")
|
|
16
|
+
.update(`${imagePath}:${targetModel}:${userIntent}`)
|
|
17
|
+
.digest("hex")
|
|
18
|
+
.slice(0, 12);
|
|
19
|
+
return (0, envelope_1.createEnvelope)("success", `Synthesized enhancement and relighting prompts tailored for '${result.targetModel}' with calibrated parameters.`, result, {
|
|
20
|
+
evidence: {
|
|
21
|
+
inputsDigest: `sha256:${hash}`,
|
|
22
|
+
sources: [{ label: "Input Image", uri: `file://${imagePath}` }],
|
|
23
|
+
},
|
|
24
|
+
nextActions: [
|
|
25
|
+
`Paste the 'relightingPrompt' into your ${result.targetModel} generator.`,
|
|
26
|
+
`Apply suggested denoising strength: ${result.recommendedParameters.denoising_strength}.`,
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const isAppErr = err instanceof errors_1.AppError;
|
|
32
|
+
const msg = err.message || "Prompt synthesis failed.";
|
|
33
|
+
const code = isAppErr ? err.code : "INTERNAL_ERROR";
|
|
34
|
+
const hint = isAppErr ? err.actionableHint : "Verify image accessibility and parameters.";
|
|
35
|
+
return (0, envelope_1.createEnvelope)("failed", `Prompt synthesis failed: ${msg}`, { error_code: code, actionable_hint: hint }, {
|
|
36
|
+
warnings: [msg],
|
|
37
|
+
nextActions: [hint],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Architecture Specification: `mcp-relight-harmonize`
|
|
2
|
+
|
|
3
|
+
> **MarwanDevSpace Architecture Specification**
|
|
4
|
+
> Enterprise-grade Model Context Protocol (MCP) server, npm-distributed binary, and Antigravity Skill.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. System Overview
|
|
9
|
+
|
|
10
|
+
`mcp-relight-harmonize` combines deterministic computer vision algorithms, physical color science, and generative AI prompt engineering into a cohesive, composable MCP system.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
┌─────────────────────────────────────────────────────────┐
|
|
14
|
+
│ Client Boundary (Antigravity IDE / Claude Desktop) │
|
|
15
|
+
└────────────────────────────┬────────────────────────────┘
|
|
16
|
+
│ stdio (JSON-RPC 2.0)
|
|
17
|
+
▼
|
|
18
|
+
┌─────────────────────────────────────────────────────────┐
|
|
19
|
+
│ server.py (FastMCP Protocol Assembly) │
|
|
20
|
+
└────────────┬───────────────┬──────────────┬─────────────┘
|
|
21
|
+
│ │ │
|
|
22
|
+
┌──────────────────┼───────────────┼──────────────┼─────────────────┐
|
|
23
|
+
▼ ▼ ▼ ▼ ▼
|
|
24
|
+
analyze_optical_profile generate_relight harmonize_comp synthesize_prompt optical://presets
|
|
25
|
+
│ │ │ │ │
|
|
26
|
+
▼ ▼ ▼ ▼ ▼
|
|
27
|
+
src/domain/optical src/domain/relight src/domain/harm src/domain/prompt src/resources
|
|
28
|
+
└──────────────────┴───────────────┴──────────────┴─────────────────┘
|
|
29
|
+
│
|
|
30
|
+
┌───────────────┴───────────────┐
|
|
31
|
+
│ OpenCV & NumPy & Colour Math │
|
|
32
|
+
└───────────────┬───────────────┘
|
|
33
|
+
│
|
|
34
|
+
┌───────────────┴───────────────┐
|
|
35
|
+
│ Filesystem Cache & IO │
|
|
36
|
+
└───────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Layering, Contracts, and Boundaries
|
|
42
|
+
|
|
43
|
+
| Layer | Path | Responsibility | Invariants |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| **Protocol / Assembly** | `server.py` | Registers tools and resources with FastMCP. | No raw business math; routes requests to tool adapters. |
|
|
46
|
+
| **Tool Adapters** | `src/tools/` | Translates protocol arguments, executes domain calls, and wraps outputs in the standard result envelope. | Never raises uncaught exceptions to the client; catches and normalizes errors. |
|
|
47
|
+
| **Domain Logic** | `src/domain/` | Pure mathematical operations (CCT, surface normal gradients, Reinhard color transfer, Poisson cloning, prompt templates). | Pure computational logic; no MCP SDK or transport dependencies. |
|
|
48
|
+
| **Contracts** | `src/contracts/` | Pydantic models, schemas, and typed enums. | Source of truth for validation. |
|
|
49
|
+
| **Core Primitives** | `src/core/` | Security guards (`security.py`), error hierarchy (`errors.py`), envelope helpers (`envelope.py`). | Reusable across tools and domain handlers. |
|
|
50
|
+
| **Resources** | `src/resources/` | Static/addressable context (`optical://presets`). | Read-only; zero mutation side-effects. |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. Tool Interconnection & Pipeline Chaining Standards
|
|
55
|
+
|
|
56
|
+
In the MarwanDevSpace design, tools form an interconnected, deterministic pipeline. Downstream tools ingest the physical measurements established by upstream analyzers:
|
|
57
|
+
|
|
58
|
+
```mermaid
|
|
59
|
+
graph TD
|
|
60
|
+
A["Input Image"] --> B["analyze_optical_profile"]
|
|
61
|
+
B -->|CCT, Azimuth, Elevation, Contrast| C["synthesize_diffusion_prompt"]
|
|
62
|
+
B -->|Profile Evidence Digest| D["generate_relight_variations"]
|
|
63
|
+
A --> E["harmonize_composite"]
|
|
64
|
+
E -->|Composite Image + Grounding| C
|
|
65
|
+
D --> F["4 Relit Variations (Ambient, Dramatic, Rim, Mood)"]
|
|
66
|
+
C --> G["Model Prompts (Flux, Midjourney, SDXL, IC-Light)"]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Evidence Chaining Rules
|
|
70
|
+
1. **Inputs Digest:** Each tool computes a canonical `sha256` digest of inputs (`evidence.inputsDigest`).
|
|
71
|
+
2. **Artifact Provenance:** Output files written to disk are listed in `evidence.artifacts` with local file URIs.
|
|
72
|
+
3. **Next-Action Guidance:** Every response provides human- and agent-readable hints in `nextActions` to transition smoothly to the next pipeline stage.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. Standard Result Envelope
|
|
77
|
+
|
|
78
|
+
All MCP tools adhere to the MarwanDevSpace Standard Result Envelope:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"status": "success | partial | blocked | failed",
|
|
83
|
+
"summary": "Human-readable executive summary of the outcome",
|
|
84
|
+
"data": { /* Domain-specific typed payload */ },
|
|
85
|
+
"warnings": [ /* Non-fatal warnings */ ],
|
|
86
|
+
"evidence": {
|
|
87
|
+
"inputsDigest": "sha256:...",
|
|
88
|
+
"sources": [ { "label": "...", "uri": "file://..." } ],
|
|
89
|
+
"artifacts": [ { "label": "...", "uri": "file://...", "sha256": "..." } ]
|
|
90
|
+
},
|
|
91
|
+
"nextActions": [ "Suggested subsequent tool calls" ]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 5. NPM & GitHub Distribution Architecture
|
|
98
|
+
|
|
99
|
+
The project is dual-packaged for Python and Node.js ecosystems:
|
|
100
|
+
|
|
101
|
+
- **`package.json`**: NPM package manifest enabling installation and execution via `npx mcp-relight-harmonize`.
|
|
102
|
+
- **`bin/mcp-relight-harmonize.js`**: Cross-platform Node.js launcher that detects local virtual environments (`.venv`) or system Python and establishes stdio stdio communication.
|
|
103
|
+
- **`.github/workflows/ci.yml`**: Continuous Integration testing across Python 3.10/3.11 and Node 18/20, running Pytest, launcher verification, and `npm pack --dry-run`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 6. Antigravity Skill Ecosystem (`.agents/skills/`)
|
|
108
|
+
|
|
109
|
+
The repository integrates directly into the Antigravity agent architecture as an on-demand skill:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
.agents/skills/mcp-relight-harmonize/
|
|
113
|
+
├── SKILL.md # Main agent instructions with YAML frontmatter
|
|
114
|
+
├── references/
|
|
115
|
+
│ ├── optical_math.md # CIE XYZ, McCamy CCT, Normal tensors, Reinhard
|
|
116
|
+
│ ├── tool_orchestration.md # Interconnection criteria, evidence chaining, fallback rules
|
|
117
|
+
│ └── diffusion_guide.md # Prompt tokens & parameters for Flux/MJ/SDXL/IC-Light
|
|
118
|
+
└── scripts/
|
|
119
|
+
├── run_pipeline.py # Standalone CLI pipeline runner
|
|
120
|
+
└── verify_server.py # Self-contained tool & resource verifier
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 7. Master Persona Reference (`MASTER.md`)
|
|
126
|
+
|
|
127
|
+
The governance, technical standards, and voice of the repository are anchored in [MASTER.md](../MASTER.md), enforcing protocol purity, optical realism, security boundaries, and release quality gates.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Operational Runbook: `mcp-relight-harmonize`
|
|
2
|
+
|
|
3
|
+
## 1. Prerequisites
|
|
4
|
+
- Node.js >= 18.0.0
|
|
5
|
+
- npm >= 9.0.0
|
|
6
|
+
|
|
7
|
+
## 2. Setup and Compilation
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
npm run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 3. Verification & Testing
|
|
14
|
+
Execute automated test suite:
|
|
15
|
+
```bash
|
|
16
|
+
npm test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Perform runtime verification:
|
|
20
|
+
```bash
|
|
21
|
+
npm run verify
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 4. Configuration
|
|
25
|
+
| Environment Variable | Default | Description |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `OUTPUT_CACHE_DIR` | `./generated_variations` | Destination folder for relit variations and harmonized composites. |
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Security & Trust Boundaries: `mcp-relight-harmonize`
|
|
2
|
+
|
|
3
|
+
## 1. Threat Model
|
|
4
|
+
The server operates locally via `stdio` transport. Although executed within the host environment, it implements proactive defenses against malicious or unverified inputs.
|
|
5
|
+
|
|
6
|
+
| Threat | Risk | Mitigation |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| **Path Traversal (`../..`)** | Unauthorized file disclosure | `validate_and_resolve_path()` canonicalizes paths with `Path.resolve()` and checks boundaries. |
|
|
9
|
+
| **Unsupported / Malicious Files** | Parsing bugs or remote code execution | Strict extension allowlist (`.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`, `.tiff`) and image header validation. |
|
|
10
|
+
| **Denial of Service (Decompression Bombs)** | Memory exhaustion | File size cap (`MAX_FILE_SIZE_BYTES = 50 MB`) and dimension validation prior to processing. |
|
|
11
|
+
| **Secret Leakage** | Exposure of private paths / tokens | Standard error envelopes redact raw system internals, returning normalized error codes and actionable hints. |
|
|
12
|
+
|
|
13
|
+
## 2. Destructive Operations
|
|
14
|
+
- `analyze_optical_profile`: Read-only.
|
|
15
|
+
- `synthesize_diffusion_prompt`: Read-only / pure computation.
|
|
16
|
+
- `generate_relight_variations`: Writes variation images to designated output directory (`OUTPUT_CACHE_DIR`).
|
|
17
|
+
- `harmonize_composite`: Writes composited image to designated output directory.
|
package/mcp_config.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-relight-harmonize",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"author": "MarwanDevSpace <dev@marwandevspace.io>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp-relight-harmonize": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/MarwanDevSpace/mcp-relight-harmonize.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/MarwanDevSpace/mcp-relight-harmonize/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/MarwanDevSpace/mcp-relight-harmonize#readme",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"mcp-server",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"antigravity",
|
|
24
|
+
"relight",
|
|
25
|
+
"harmonize",
|
|
26
|
+
"optical-analysis",
|
|
27
|
+
"gpt-image",
|
|
28
|
+
"nano-banana",
|
|
29
|
+
"marwandevspace"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"start": "node dist/index.js",
|
|
37
|
+
"dev": "tsc --watch",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"verify": "node dist/index.js --verify"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
43
|
+
"jpeg-js": "^0.4.4",
|
|
44
|
+
"pngjs": "^7.0.0",
|
|
45
|
+
"zod": "^3.24.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.13.8",
|
|
49
|
+
"@types/pngjs": "^6.0.5",
|
|
50
|
+
"typescript": "^5.7.3",
|
|
51
|
+
"vitest": "^3.0.7"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"package.json",
|
|
56
|
+
"MASTER.md",
|
|
57
|
+
"mcp_config.json",
|
|
58
|
+
".agents",
|
|
59
|
+
"docs",
|
|
60
|
+
"README.md"
|
|
61
|
+
]
|
|
62
|
+
}
|