@synthetic-ai/photoshop-mcp 1.0.5 → 1.0.7-dev.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/package.json +1 -1
- package/server.mjs +92 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synthetic-ai/photoshop-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7-dev.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Control Adobe Photoshop with 409 AI tools via MCP — layers, text, masks, filters, adjustments, smart objects, export. By Synthetic (synthetic.com.ar).",
|
|
6
6
|
"homepage": "https://synthetic.com.ar/photoshop-mcp",
|
package/server.mjs
CHANGED
|
@@ -17,10 +17,45 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
17
17
|
import WebSocket from "ws";
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import https from "https";
|
|
20
|
+
import os from "os";
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import crypto from "crypto";
|
|
20
24
|
|
|
21
25
|
const PORT = Number(process.env.PS_MCP_PORT || 41219);
|
|
22
26
|
const WS_URL = "ws://127.0.0.1:" + PORT;
|
|
23
27
|
|
|
28
|
+
// ── Device identity (for the 2-device license cap) ─────────────────────
|
|
29
|
+
// Shared across all Synthetic MCP packages (ae-mcp, premiere-mcp,
|
|
30
|
+
// photoshop-mcp) on purpose: one physical machine should count as ONE
|
|
31
|
+
// device against the cap, not one per product installed on it. Generated
|
|
32
|
+
// once and persisted locally — not real hardware fingerprinting, just a
|
|
33
|
+
// stable per-installation id that survives restarts and `npx` re-runs.
|
|
34
|
+
function getMachineId() {
|
|
35
|
+
try {
|
|
36
|
+
const dir = path.join(os.homedir(), ".synthetic");
|
|
37
|
+
const file = path.join(dir, "device-id");
|
|
38
|
+
if (fs.existsSync(file)) {
|
|
39
|
+
const id = fs.readFileSync(file, "utf8").trim();
|
|
40
|
+
if (id) return id;
|
|
41
|
+
}
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
const id = crypto.randomUUID();
|
|
44
|
+
fs.writeFileSync(file, id, "utf8");
|
|
45
|
+
return id;
|
|
46
|
+
} catch {
|
|
47
|
+
// Filesystem unavailable/read-only — fall back to a per-process id.
|
|
48
|
+
// Worst case this machine's slot doesn't stick across restarts; it
|
|
49
|
+
// never blocks the tool from working.
|
|
50
|
+
return "volatile-" + crypto.randomUUID();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getDeviceLabel() {
|
|
54
|
+
try { return `${os.hostname()} (${process.platform})`; } catch { return null; }
|
|
55
|
+
}
|
|
56
|
+
const MACHINE_ID = getMachineId();
|
|
57
|
+
const DEVICE_LABEL = getDeviceLabel();
|
|
58
|
+
|
|
24
59
|
// ── License validation ──────────────────────────────────────────────────
|
|
25
60
|
// Same pattern as ae-mcp/premiere-mcp: validate once, cache for an hour,
|
|
26
61
|
// gate everything except the free diagnostic tools. PS_MCP_LICENSE is set
|
|
@@ -60,7 +95,12 @@ async function checkLicense() {
|
|
|
60
95
|
return _licenseCache;
|
|
61
96
|
}
|
|
62
97
|
try {
|
|
63
|
-
const result = await _httpsPost(LICENSE_VALIDATION_URL, {
|
|
98
|
+
const result = await _httpsPost(LICENSE_VALIDATION_URL, {
|
|
99
|
+
key: LICENSE_KEY,
|
|
100
|
+
product: "photoshop-mcp",
|
|
101
|
+
machineId: MACHINE_ID,
|
|
102
|
+
deviceLabel: DEVICE_LABEL,
|
|
103
|
+
});
|
|
64
104
|
_licenseCache = { ...(result || {}), checkedAt: Date.now() };
|
|
65
105
|
if (!result) _licenseCache = { valid: false, message: "License server unreachable. Will retry next call.", checkedAt: Date.now() };
|
|
66
106
|
} catch {
|
|
@@ -1048,5 +1088,56 @@ GAPS CONOCIDOS (verificado en vivo contra Photoshop 27.9.1, 2026-09-05, 3 rondas
|
|
|
1048
1088
|
|
|
1049
1089
|
Cualquier cosa no nombrada → run_jsx (ExtendScript) o run_action (Action Manager).`));
|
|
1050
1090
|
|
|
1091
|
+
// ── CLI: --doctor ────────────────────────────────────────────────────────
|
|
1092
|
+
// Same pattern as ae-mcp/premiere-mcp: a standalone diagnostic run outside
|
|
1093
|
+
// the MCP protocol (`npx @synthetic-ai/photoshop-mcp --doctor`), meant to be
|
|
1094
|
+
// pasted into a support ticket before writing in. Checks Node version,
|
|
1095
|
+
// license validity, and whether the CEP bridge is actually reachable on
|
|
1096
|
+
// PORT — the WebSocket-specific equivalent of ae-mcp's "is the bridge temp
|
|
1097
|
+
// dir writable" check, since this product's bridge is a socket, not a file.
|
|
1098
|
+
const cliArgs = process.argv.slice(2);
|
|
1099
|
+
if (cliArgs.includes("--doctor")) {
|
|
1100
|
+
(async () => {
|
|
1101
|
+
const lines = [];
|
|
1102
|
+
const ok = (msg) => lines.push("OK " + msg);
|
|
1103
|
+
const warn = (msg) => lines.push("WARN " + msg);
|
|
1104
|
+
const fail = (msg) => lines.push("FAIL " + msg);
|
|
1105
|
+
const info = (msg) => lines.push("INFO " + msg);
|
|
1106
|
+
|
|
1107
|
+
lines.push("Synthetic Photoshop MCP — doctor");
|
|
1108
|
+
lines.push("");
|
|
1109
|
+
|
|
1110
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
1111
|
+
if (nodeMajor >= 18) ok(`Node.js ${process.version} (>=18 required)`);
|
|
1112
|
+
else fail(`Node.js ${process.version} — needs to be 18 or newer`);
|
|
1113
|
+
|
|
1114
|
+
if (!LICENSE_KEY) {
|
|
1115
|
+
fail("PS_MCP_LICENSE is not set. Get a license at https://synthetic.com.ar/photoshop-mcp");
|
|
1116
|
+
} else {
|
|
1117
|
+
const result = await checkLicense();
|
|
1118
|
+
if (result.valid) ok(`License valid (${result.plan || "active"})`);
|
|
1119
|
+
else fail(`License check failed: ${result.message || "invalid"}`);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
info(`Bridge: connecting to ${WS_URL} (checking the PS-MCP Bridge panel is open in Photoshop)...`);
|
|
1123
|
+
const bridgeUp = await new Promise((resolve) => {
|
|
1124
|
+
let s;
|
|
1125
|
+
try { s = new WebSocket(WS_URL); } catch { resolve(false); return; }
|
|
1126
|
+
const timer = setTimeout(() => { try { s.terminate(); } catch {} resolve(false); }, 3000);
|
|
1127
|
+
s.on("open", () => { clearTimeout(timer); try { s.close(); } catch {} resolve(true); });
|
|
1128
|
+
s.on("error", () => { clearTimeout(timer); resolve(false); });
|
|
1129
|
+
});
|
|
1130
|
+
if (bridgeUp) ok("Bridge reachable — PS-MCP Bridge panel is running inside Photoshop.");
|
|
1131
|
+
else fail("Bridge not reachable. Open Photoshop with the PS-MCP Bridge panel (Window → Extensions (Legacy) → PS-MCP Bridge) and confirm it says \"Connected\".");
|
|
1132
|
+
|
|
1133
|
+
lines.push("");
|
|
1134
|
+
lines.push("Still stuck? Paste this output into a ticket at https://synthetic.com.ar/dashboard/support");
|
|
1135
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1136
|
+
process.exit(0);
|
|
1137
|
+
})();
|
|
1138
|
+
} else {
|
|
1139
|
+
|
|
1051
1140
|
const transport = new StdioServerTransport();
|
|
1052
1141
|
await server.connect(transport);
|
|
1142
|
+
|
|
1143
|
+
}
|