@topazlabs/mcp 0.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.
- package/README.md +96 -0
- package/dist/api/client.d.ts +31 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +150 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/models.d.ts +19 -0
- package/dist/api/models.d.ts.map +1 -0
- package/dist/api/models.js +75 -0
- package/dist/api/models.js.map +1 -0
- package/dist/api/types.d.ts +40 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/apps/viewer.html +51 -0
- package/dist/lib/create-main.d.ts +9 -0
- package/dist/lib/create-main.d.ts.map +1 -0
- package/dist/lib/create-main.js +27 -0
- package/dist/lib/create-main.js.map +1 -0
- package/dist/lib/errors.d.ts +16 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +17 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/fetch-url.d.ts +18 -0
- package/dist/lib/fetch-url.d.ts.map +1 -0
- package/dist/lib/fetch-url.js +239 -0
- package/dist/lib/fetch-url.js.map +1 -0
- package/dist/lib/http-server.d.ts +18 -0
- package/dist/lib/http-server.d.ts.map +1 -0
- package/dist/lib/http-server.js +225 -0
- package/dist/lib/http-server.js.map +1 -0
- package/dist/lib/path-security.d.ts +21 -0
- package/dist/lib/path-security.d.ts.map +1 -0
- package/dist/lib/path-security.js +210 -0
- package/dist/lib/path-security.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +108 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/compare.d.ts +9 -0
- package/dist/tools/compare.d.ts.map +1 -0
- package/dist/tools/compare.js +133 -0
- package/dist/tools/compare.js.map +1 -0
- package/dist/tools/enhance.d.ts +22 -0
- package/dist/tools/enhance.d.ts.map +1 -0
- package/dist/tools/enhance.js +175 -0
- package/dist/tools/enhance.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path security ā validates file paths against allowed directories.
|
|
3
|
+
*
|
|
4
|
+
* - stdio mode: CWD + temp + TOPAZ_ALLOWED_DIRS
|
|
5
|
+
* - http mode: temp only (no filesystem access)
|
|
6
|
+
* - Always blocks sensitive paths (/etc, ~/.ssh, etc.)
|
|
7
|
+
* - Blocks symlinks that escape allowed directories
|
|
8
|
+
*/
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
let config = { mode: "stdio" };
|
|
13
|
+
/** Sensitive directory prefixes that should never be accessed */
|
|
14
|
+
const SENSITIVE_PREFIXES = [
|
|
15
|
+
"/etc/",
|
|
16
|
+
"/root/",
|
|
17
|
+
"/proc/",
|
|
18
|
+
"/sys/",
|
|
19
|
+
"/dev/",
|
|
20
|
+
"/var/run/",
|
|
21
|
+
];
|
|
22
|
+
function getSensitivePrefixes() {
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
return [
|
|
25
|
+
...SENSITIVE_PREFIXES,
|
|
26
|
+
path.join(home, ".ssh") + "/",
|
|
27
|
+
path.join(home, ".gnupg") + "/",
|
|
28
|
+
path.join(home, ".aws") + "/",
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
function isSensitivePath(p) {
|
|
32
|
+
const withTrailing = p.endsWith("/") ? p : p + "/";
|
|
33
|
+
const prefixes = getSensitivePrefixes();
|
|
34
|
+
for (const prefix of prefixes) {
|
|
35
|
+
if (withTrailing.startsWith(prefix) || p === prefix.slice(0, -1)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function resolveReal(p) {
|
|
42
|
+
try {
|
|
43
|
+
return fs.realpathSync(p);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a path safely, handling the case where the file doesn't exist yet.
|
|
51
|
+
* For non-existent files, resolves the parent directory (which must exist)
|
|
52
|
+
* and joins the filename, so symlinks in parent dirs are caught.
|
|
53
|
+
*/
|
|
54
|
+
function resolveRealSafe(p) {
|
|
55
|
+
try {
|
|
56
|
+
return fs.realpathSync(p);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// File doesn't exist ā resolve the parent directory instead
|
|
60
|
+
const parentDir = path.dirname(p);
|
|
61
|
+
try {
|
|
62
|
+
const realParent = fs.realpathSync(parentDir);
|
|
63
|
+
return path.join(realParent, path.basename(p));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Parent doesn't exist either ā reject by returning the unresolved
|
|
67
|
+
// path; the allowed-dir check below will handle it
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Walk every component of a path checking for symlinks that escape allowed dirs.
|
|
74
|
+
* Returns true if any symlink component resolves outside allowed directories.
|
|
75
|
+
*/
|
|
76
|
+
function hasSymlinkEscape(filePath) {
|
|
77
|
+
const allowed = getAllowedDirs();
|
|
78
|
+
const parts = filePath.split(path.sep);
|
|
79
|
+
let current = parts[0] === "" ? path.sep : parts[0];
|
|
80
|
+
for (let i = parts[0] === "" ? 1 : 1; i < parts.length; i++) {
|
|
81
|
+
current = path.join(current, parts[i]);
|
|
82
|
+
try {
|
|
83
|
+
const stat = fs.lstatSync(current);
|
|
84
|
+
if (stat.isSymbolicLink()) {
|
|
85
|
+
const target = fs.realpathSync(current);
|
|
86
|
+
// A symlink is safe if:
|
|
87
|
+
// 1. It resolves within an allowed directory, OR
|
|
88
|
+
// 2. It resolves to an ancestor of an allowed directory
|
|
89
|
+
// (e.g. macOS /var ā /private/var when allowed dir is /private/var/folders/.../T)
|
|
90
|
+
// The final resolved path is already verified by isWithinAllowedDirs().
|
|
91
|
+
const isWithinAllowed = allowed.some((dir) => target === dir || target.startsWith(dir + path.sep));
|
|
92
|
+
const isAncestorOfAllowed = allowed.some((dir) => dir.startsWith(target + path.sep));
|
|
93
|
+
if (!isWithinAllowed && !isAncestorOfAllowed) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Component doesn't exist yet ā stop walking (OK for write paths,
|
|
100
|
+
// the parent-resolution check handles the rest)
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
function getAllowedDirs() {
|
|
107
|
+
const tmpDir = path.resolve(os.tmpdir());
|
|
108
|
+
const tmpDirReal = resolveReal(tmpDir);
|
|
109
|
+
if (config.mode === "http") {
|
|
110
|
+
const dirs = [tmpDir];
|
|
111
|
+
if (tmpDirReal !== tmpDir)
|
|
112
|
+
dirs.push(tmpDirReal);
|
|
113
|
+
return dirs;
|
|
114
|
+
}
|
|
115
|
+
// stdio mode: CWD + temp + configured dirs
|
|
116
|
+
const cwd = path.resolve(process.cwd());
|
|
117
|
+
const cwdReal = resolveReal(cwd);
|
|
118
|
+
const dirs = [cwd, tmpDir];
|
|
119
|
+
if (cwdReal !== cwd)
|
|
120
|
+
dirs.push(cwdReal);
|
|
121
|
+
if (tmpDirReal !== tmpDir)
|
|
122
|
+
dirs.push(tmpDirReal);
|
|
123
|
+
// Additional dirs from env
|
|
124
|
+
const envDirs = process.env.TOPAZ_ALLOWED_DIRS;
|
|
125
|
+
if (envDirs) {
|
|
126
|
+
for (const d of envDirs.split(",")) {
|
|
127
|
+
const trimmed = d.trim();
|
|
128
|
+
if (trimmed) {
|
|
129
|
+
const resolved = path.resolve(trimmed);
|
|
130
|
+
dirs.push(resolved);
|
|
131
|
+
const real = resolveReal(resolved);
|
|
132
|
+
if (real !== resolved)
|
|
133
|
+
dirs.push(real);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Programmatic allowed dirs
|
|
138
|
+
if (config.allowedDirs) {
|
|
139
|
+
for (const d of config.allowedDirs) {
|
|
140
|
+
const resolved = path.resolve(d);
|
|
141
|
+
dirs.push(resolved);
|
|
142
|
+
const real = resolveReal(resolved);
|
|
143
|
+
if (real !== resolved)
|
|
144
|
+
dirs.push(real);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return dirs;
|
|
148
|
+
}
|
|
149
|
+
function isWithinAllowedDirs(realPath) {
|
|
150
|
+
const allowed = getAllowedDirs();
|
|
151
|
+
return allowed.some((dir) => realPath === dir || realPath.startsWith(dir + path.sep));
|
|
152
|
+
}
|
|
153
|
+
function validatePathForRead(inputPath) {
|
|
154
|
+
const resolved = path.resolve(inputPath);
|
|
155
|
+
// Resolve symlinks ā file must exist for reads
|
|
156
|
+
const realPath = resolveRealSafe(resolved);
|
|
157
|
+
// Block sensitive paths
|
|
158
|
+
if (isSensitivePath(realPath)) {
|
|
159
|
+
throw new Error("Access denied: path is in a restricted location");
|
|
160
|
+
}
|
|
161
|
+
// Check allowed directories
|
|
162
|
+
if (!isWithinAllowedDirs(realPath)) {
|
|
163
|
+
throw new Error("Access denied: path is outside allowed directories");
|
|
164
|
+
}
|
|
165
|
+
// Walk path components to catch symlink escapes
|
|
166
|
+
if (hasSymlinkEscape(resolved)) {
|
|
167
|
+
throw new Error("Access denied: path contains a symlink that escapes allowed directories");
|
|
168
|
+
}
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
function validatePathForWrite(inputPath) {
|
|
172
|
+
const resolved = path.resolve(inputPath);
|
|
173
|
+
// For writes, resolve via parent directory so symlinked parents are caught
|
|
174
|
+
const realPath = resolveRealSafe(resolved);
|
|
175
|
+
// Block sensitive paths
|
|
176
|
+
if (isSensitivePath(realPath)) {
|
|
177
|
+
throw new Error("Access denied: path is in a restricted location");
|
|
178
|
+
}
|
|
179
|
+
// Check allowed directories using the real (symlink-resolved) path
|
|
180
|
+
if (!isWithinAllowedDirs(realPath)) {
|
|
181
|
+
throw new Error("Access denied: path is outside allowed directories");
|
|
182
|
+
}
|
|
183
|
+
// Walk path components to catch symlink escapes
|
|
184
|
+
if (hasSymlinkEscape(resolved)) {
|
|
185
|
+
throw new Error("Access denied: path contains a symlink that escapes allowed directories");
|
|
186
|
+
}
|
|
187
|
+
return resolved;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Configure path security. Call once at startup before any tool handlers run.
|
|
191
|
+
* Default: stdio mode (CWD + temp allowed).
|
|
192
|
+
*/
|
|
193
|
+
export function configurePathSecurity(options) {
|
|
194
|
+
config = { ...options };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Validate a file path for reading. Returns the resolved absolute path.
|
|
198
|
+
* Throws if the path is outside allowed directories or in a sensitive location.
|
|
199
|
+
*/
|
|
200
|
+
export function validateReadPath(inputPath) {
|
|
201
|
+
return validatePathForRead(inputPath);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Validate a file path for writing. Returns the resolved absolute path.
|
|
205
|
+
* Throws if the path is outside allowed directories or in a sensitive location.
|
|
206
|
+
*/
|
|
207
|
+
export function validateWritePath(inputPath) {
|
|
208
|
+
return validatePathForWrite(inputPath);
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=path-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.js","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
|
package/dist/main.js
ADDED
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,UAAU,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACrC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqBpE,wBAAgB,YAAY,IAAI,SAAS,CA4HxC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { toolError } from "./lib/errors.js";
|
|
7
|
+
import { TopazClient } from "./api/client.js";
|
|
8
|
+
import { handleEnhance } from "./tools/enhance.js";
|
|
9
|
+
import { handleCompare } from "./tools/compare.js";
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
function getViewerHtml() {
|
|
12
|
+
// In dist, apps/ is a sibling of the compiled server.js
|
|
13
|
+
const appPath = join(__dirname, "apps", "viewer.html");
|
|
14
|
+
if (!existsSync(appPath)) {
|
|
15
|
+
return "<html><body>Viewer not built. Run npm run build:app</body></html>";
|
|
16
|
+
}
|
|
17
|
+
return readFileSync(appPath, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
export function createServer() {
|
|
20
|
+
let client;
|
|
21
|
+
function getClient() {
|
|
22
|
+
if (!client) {
|
|
23
|
+
const apiKey = process.env.TOPAZ_API_KEY;
|
|
24
|
+
if (!apiKey) {
|
|
25
|
+
throw new Error("TOPAZ_API_KEY environment variable is required. Set it before making API calls.");
|
|
26
|
+
}
|
|
27
|
+
client = new TopazClient({ apiKey });
|
|
28
|
+
}
|
|
29
|
+
return client;
|
|
30
|
+
}
|
|
31
|
+
const server = new McpServer({ name: "@topazlabs/mcp", version: "0.1.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
32
|
+
// --- Resource: Before/After Viewer ---
|
|
33
|
+
const viewerUri = "ui://topaz/viewer";
|
|
34
|
+
server.resource("viewer", viewerUri, {
|
|
35
|
+
description: "Interactive before/after image comparison viewer",
|
|
36
|
+
mimeType: "text/html",
|
|
37
|
+
}, async () => ({
|
|
38
|
+
contents: [
|
|
39
|
+
{
|
|
40
|
+
uri: viewerUri,
|
|
41
|
+
mimeType: "text/html",
|
|
42
|
+
text: getViewerHtml(),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}));
|
|
46
|
+
// --- Tool: enhance ---
|
|
47
|
+
server.tool("enhance", "Enhance an image using Topaz Labs AI. Upscales, sharpens, and improves quality. Accepts a local file path or URL.", {
|
|
48
|
+
image: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe("Local file path or URL to the image"),
|
|
51
|
+
size: z
|
|
52
|
+
.object({
|
|
53
|
+
width: z.number().int().min(1).max(32000).optional(),
|
|
54
|
+
height: z.number().int().min(1).max(32000).optional(),
|
|
55
|
+
scale: z.number().min(0.1).max(16).optional(),
|
|
56
|
+
})
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Output size. {width, height} for exact, {width} or {height} for proportional, {scale} for multiplier. Blank auto-upscales to 4K."),
|
|
59
|
+
model: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Model name. Auto-selects if omitted (Wonder for small, Standard V2 for large). Options: Standard V2, Low Resolution V2, CGI, High Fidelity V2, Text Refine, Redefine, Recovery V2, Standard MAX, Wonder, Bloom"),
|
|
63
|
+
}, async (params) => {
|
|
64
|
+
try {
|
|
65
|
+
return await handleEnhance({
|
|
66
|
+
image: params.image,
|
|
67
|
+
size: params.size,
|
|
68
|
+
model: params.model,
|
|
69
|
+
}, getClient());
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return toolError(err instanceof Error ? err.message : String(err));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// --- Tool: compare ---
|
|
76
|
+
server.tool("compare", "Compare two images side-by-side with an interactive before/after slider viewer. Supports zoom, pan, and toggle views.", {
|
|
77
|
+
before: z
|
|
78
|
+
.string()
|
|
79
|
+
.describe('Path or URL to the "before" image'),
|
|
80
|
+
after: z
|
|
81
|
+
.string()
|
|
82
|
+
.describe('Path or URL to the "after" image'),
|
|
83
|
+
label_before: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.default("Before")
|
|
87
|
+
.describe("Label for the before image"),
|
|
88
|
+
label_after: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.default("After")
|
|
92
|
+
.describe("Label for the after image"),
|
|
93
|
+
}, async (params) => {
|
|
94
|
+
try {
|
|
95
|
+
return await handleCompare({
|
|
96
|
+
before: params.before,
|
|
97
|
+
after: params.after,
|
|
98
|
+
label_before: params.label_before,
|
|
99
|
+
label_after: params.label_after,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return toolError(err instanceof Error ? err.message : String(err));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return server;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export interface CompareInput {
|
|
3
|
+
before: string;
|
|
4
|
+
after: string;
|
|
5
|
+
label_before?: string;
|
|
6
|
+
label_after?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function handleCompare(input: CompareInput): Promise<CallToolResult>;
|
|
9
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/tools/compare.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA2FzE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,cAAc,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname, extname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { validateReadPath } from "../lib/path-security.js";
|
|
6
|
+
import { loadImage } from "../lib/fetch-url.js";
|
|
7
|
+
/** Maximum image size in bytes before we resize for the comparison viewer */
|
|
8
|
+
const MAX_IMAGE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
function getViewerHtml() {
|
|
11
|
+
const appPath = join(__dirname, "..", "apps", "viewer.html");
|
|
12
|
+
if (!existsSync(appPath)) {
|
|
13
|
+
// Fallback to dist/apps path
|
|
14
|
+
const distPath = join(__dirname, "apps", "viewer.html");
|
|
15
|
+
if (!existsSync(distPath)) {
|
|
16
|
+
return "<html><body>Viewer not built. Run npm run build:app</body></html>";
|
|
17
|
+
}
|
|
18
|
+
return readFileSync(distPath, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
return readFileSync(appPath, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function isUrl(s) {
|
|
23
|
+
return /^https?:\/\//i.test(s);
|
|
24
|
+
}
|
|
25
|
+
function mimeForExt(ext) {
|
|
26
|
+
const map = {
|
|
27
|
+
".jpg": "image/jpeg",
|
|
28
|
+
".jpeg": "image/jpeg",
|
|
29
|
+
".png": "image/png",
|
|
30
|
+
".webp": "image/webp",
|
|
31
|
+
".tiff": "image/tiff",
|
|
32
|
+
".tif": "image/tiff",
|
|
33
|
+
".gif": "image/gif",
|
|
34
|
+
".bmp": "image/bmp",
|
|
35
|
+
};
|
|
36
|
+
return map[ext.toLowerCase()] ?? "image/png";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ensure a buffer is within the size limit. If it exceeds MAX_IMAGE_BYTES,
|
|
40
|
+
* progressively resize it down until it fits, outputting as JPEG for
|
|
41
|
+
* efficient compression.
|
|
42
|
+
*/
|
|
43
|
+
async function enforceImageSizeLimit(buf) {
|
|
44
|
+
if (buf.length <= MAX_IMAGE_BYTES) {
|
|
45
|
+
return { buffer: buf, mime: "" }; // mime="" signals: use the original mime
|
|
46
|
+
}
|
|
47
|
+
// Resize down iteratively ā halve the dimensions each step
|
|
48
|
+
let img = sharp(buf);
|
|
49
|
+
const meta = await img.metadata();
|
|
50
|
+
let width = meta.width ?? 4000;
|
|
51
|
+
let height = meta.height ?? 4000;
|
|
52
|
+
// Start by trying a JPEG conversion at current size
|
|
53
|
+
let result = await img.jpeg({ quality: 85 }).toBuffer();
|
|
54
|
+
if (result.length <= MAX_IMAGE_BYTES) {
|
|
55
|
+
return { buffer: result, mime: "image/jpeg" };
|
|
56
|
+
}
|
|
57
|
+
// Progressively halve until it fits (max 4 iterations = 1/16th size)
|
|
58
|
+
for (let i = 0; i < 4 && result.length > MAX_IMAGE_BYTES; i++) {
|
|
59
|
+
width = Math.round(width / 2);
|
|
60
|
+
height = Math.round(height / 2);
|
|
61
|
+
img = sharp(buf).resize(width, height, { fit: "inside" });
|
|
62
|
+
result = await img.jpeg({ quality: 80 }).toBuffer();
|
|
63
|
+
}
|
|
64
|
+
return { buffer: result, mime: "image/jpeg" };
|
|
65
|
+
}
|
|
66
|
+
async function resolveImage(input) {
|
|
67
|
+
let buf;
|
|
68
|
+
let ext;
|
|
69
|
+
if (isUrl(input)) {
|
|
70
|
+
buf = await loadImage(input);
|
|
71
|
+
ext = extname(new URL(input).pathname);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const absPath = validateReadPath(input);
|
|
75
|
+
buf = readFileSync(absPath);
|
|
76
|
+
ext = extname(absPath);
|
|
77
|
+
}
|
|
78
|
+
const { buffer, mime: overrideMime } = await enforceImageSizeLimit(buf);
|
|
79
|
+
const mime = overrideMime || mimeForExt(ext);
|
|
80
|
+
return `data:${mime};base64,${buffer.toString("base64")}`;
|
|
81
|
+
}
|
|
82
|
+
export async function handleCompare(input) {
|
|
83
|
+
if (!input.before || !input.after) {
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{ type: "text", text: "Error: Both before and after are required." },
|
|
87
|
+
],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const labelBefore = input.label_before ?? "Before";
|
|
92
|
+
const labelAfter = input.label_after ?? "After";
|
|
93
|
+
try {
|
|
94
|
+
const beforeSrc = await resolveImage(input.before);
|
|
95
|
+
const afterSrc = await resolveImage(input.after);
|
|
96
|
+
// Inject data into viewer HTML
|
|
97
|
+
const viewerTemplate = getViewerHtml();
|
|
98
|
+
const safeJson = JSON.stringify({
|
|
99
|
+
before: beforeSrc,
|
|
100
|
+
after: afterSrc,
|
|
101
|
+
labelBefore,
|
|
102
|
+
labelAfter,
|
|
103
|
+
}).replace(/</g, "\\u003c");
|
|
104
|
+
const dataScript = `<script>window.__COMPARE_DATA__=${safeJson};</script>`;
|
|
105
|
+
const html = viewerTemplate.replace("</head>", `${dataScript}\n</head>`);
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `š Comparing images:\n⢠Before: ${input.before} (${labelBefore})\n⢠After: ${input.after} (${labelAfter})\n\nUse the slider to compare, scroll to zoom, double-click to toggle zoom. Keys: 1/2/3 for before/split/after.`,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: "resource",
|
|
114
|
+
resource: {
|
|
115
|
+
uri: "ui://topaz/viewer",
|
|
116
|
+
mimeType: "text/html",
|
|
117
|
+
text: html,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{ type: "text", text: `Error comparing images: ${message}` },
|
|
128
|
+
],
|
|
129
|
+
isError: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/tools/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { TopazClient } from "../api/client.js";
|
|
3
|
+
/** Parse the size parameter into target pixel dimensions. */
|
|
4
|
+
export declare function resolveSize(size: {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
scale?: number;
|
|
8
|
+
} | undefined, origWidth: number, origHeight: number): {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
export interface EnhanceInput {
|
|
13
|
+
image: string;
|
|
14
|
+
size?: {
|
|
15
|
+
width?: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
scale?: number;
|
|
18
|
+
};
|
|
19
|
+
model?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function handleEnhance(input: EnhanceInput, client: TopazClient): Promise<CallToolResult>;
|
|
22
|
+
//# sourceMappingURL=enhance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhance.d.ts","sourceRoot":"","sources":["../../src/tools/enhance.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAiB,MAAM,kBAAkB,CAAC;AAI9D,6DAA6D;AAC7D,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACrE,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmCnC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,cAAc,CAAC,CA2IzB"}
|