@creatica/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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.js +350 -0
- package/dist/index.js.map +1 -0
- package/dist/stdio.js +370 -0
- package/dist/stdio.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anup Aglawe (Creatica)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import {
|
|
3
|
+
ENGINE_VERSION,
|
|
4
|
+
renderPng,
|
|
5
|
+
renderSvg
|
|
6
|
+
} from "@creatica/engine";
|
|
7
|
+
import {
|
|
8
|
+
catalog,
|
|
9
|
+
describe,
|
|
10
|
+
jsonSchemaFor,
|
|
11
|
+
listPalettes,
|
|
12
|
+
normalizeRequest,
|
|
13
|
+
styleCard,
|
|
14
|
+
suggest
|
|
15
|
+
} from "@creatica/schema";
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
18
|
+
import { dirname, resolve } from "path";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
var hexColor = z.string().regex(
|
|
21
|
+
/^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,
|
|
22
|
+
"hex colour like #1e90ff"
|
|
23
|
+
);
|
|
24
|
+
var paletteShape = z.object({
|
|
25
|
+
colors: z.array(hexColor).min(1).max(12).optional().describe(
|
|
26
|
+
"Explicit hex colours to draw from (best for brand matching). Overrides palette/scheme."
|
|
27
|
+
),
|
|
28
|
+
palette: z.string().optional().describe(
|
|
29
|
+
'Id of a built-in palette from list_palettes, e.g. "underTheSea".'
|
|
30
|
+
),
|
|
31
|
+
primaryColor: hexColor.optional(),
|
|
32
|
+
secondaryColor: hexColor.optional(),
|
|
33
|
+
backgroundColor: hexColor.optional(),
|
|
34
|
+
gradientColor: z.boolean().optional().describe("Gradient fills instead of flat colour."),
|
|
35
|
+
useMultiColor: z.boolean().optional().describe("false = everything in primaryColor."),
|
|
36
|
+
colorSchemeType: z.enum(["Mono", "Tetrade", "Analogic"]).optional().describe("Derive a palette from primaryColor."),
|
|
37
|
+
opaqueColors: z.boolean().optional()
|
|
38
|
+
}).strict();
|
|
39
|
+
var shapeShape = z.record(z.string(), z.union([z.number(), z.boolean(), z.string()])).describe(
|
|
40
|
+
"Structure-specific options; call describe_background for the exact keys, ranges and what they do."
|
|
41
|
+
);
|
|
42
|
+
function createMcpServer(options = {}) {
|
|
43
|
+
const { hosted } = options;
|
|
44
|
+
const maxInline = options.maxInlineBytes ?? 6e5;
|
|
45
|
+
const server = new McpServer(
|
|
46
|
+
{ name: "creatica-backgrounds", version: ENGINE_VERSION },
|
|
47
|
+
{
|
|
48
|
+
instructions: [
|
|
49
|
+
"Creatica generates abstract, procedural SVG/PNG backgrounds (waves, blobs, bauhaus grids, topo lines, aurora glows\u2026) for websites, slides, social cards and apps.",
|
|
50
|
+
"Workflow: suggest_backgrounds (or list_backgrounds) with the brief \u2192 describe_background for the chosen one \u2192 generate_background with a seed. Every render is deterministic: same structure + params + seed = identical image, so keep the seed to reproduce or make on-brand variants.",
|
|
51
|
+
"For brand colours pass palette.colors as hex. For a curated look pass palette.palette from list_palettes. Prefer PNG when you need a preview; SVG for the final asset."
|
|
52
|
+
].join("\n")
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
server.registerTool(
|
|
56
|
+
"list_backgrounds",
|
|
57
|
+
{
|
|
58
|
+
title: "List backgrounds",
|
|
59
|
+
description: "Browse the catalog of background structures. Optionally filter by a free-text query, tag or mood word. Returns name, one-line summary, mood, use cases and a preview URL for each.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
query: z.string().optional().describe('Free text, e.g. "calm waves" or "geometric poster".'),
|
|
62
|
+
mood: z.string().optional().describe("A mood word, e.g. dark, playful, minimal, retro."),
|
|
63
|
+
tag: z.string().optional(),
|
|
64
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
65
|
+
},
|
|
66
|
+
annotations: {
|
|
67
|
+
readOnlyHint: true,
|
|
68
|
+
idempotentHint: true,
|
|
69
|
+
openWorldHint: false
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
async ({ query, mood, tag, limit }) => {
|
|
73
|
+
let items = catalog();
|
|
74
|
+
if (mood)
|
|
75
|
+
items = items.filter(
|
|
76
|
+
(c) => c.mood.some((m) => m.toLowerCase().includes(mood.toLowerCase()))
|
|
77
|
+
);
|
|
78
|
+
if (tag)
|
|
79
|
+
items = items.filter(
|
|
80
|
+
(c) => c.tags.some((t) => t.toLowerCase().includes(tag.toLowerCase()))
|
|
81
|
+
);
|
|
82
|
+
if (query) {
|
|
83
|
+
const ranked = suggest(query, { limit: 100 }).map((s) => s.name);
|
|
84
|
+
items = items.filter((c) => ranked.includes(c.name)).sort((a, b) => ranked.indexOf(a.name) - ranked.indexOf(b.name));
|
|
85
|
+
}
|
|
86
|
+
items = items.slice(0, limit ?? 100);
|
|
87
|
+
return json({ count: items.length, backgrounds: items });
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
server.registerTool(
|
|
91
|
+
"suggest_backgrounds",
|
|
92
|
+
{
|
|
93
|
+
title: "Suggest backgrounds for a brief",
|
|
94
|
+
description: 'Rank structures for a natural-language brief ("dark aurora glow for an AI landing page"). Returns why each matched, matching built-in palettes, and a ready-to-run example request for generate_background.',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
brief: z.string().min(2),
|
|
97
|
+
limit: z.number().int().min(1).max(20).optional()
|
|
98
|
+
},
|
|
99
|
+
annotations: {
|
|
100
|
+
readOnlyHint: true,
|
|
101
|
+
idempotentHint: true,
|
|
102
|
+
openWorldHint: false
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async ({ brief, limit }) => json({ suggestions: suggest(brief, { limit: limit ?? 5 }) })
|
|
106
|
+
);
|
|
107
|
+
server.registerTool(
|
|
108
|
+
"describe_background",
|
|
109
|
+
{
|
|
110
|
+
title: "Describe a background",
|
|
111
|
+
description: "Full style card for one structure: what it looks like, mood, where text can go, tips, and every parameter it honours with ranges, defaults and the visual effect of low/high values. Also returns the JSON Schema for a generate request.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
name: z.string().describe('Structure name from list_backgrounds, e.g. "wave".'),
|
|
114
|
+
format: z.enum(["markdown", "json"]).optional().describe(
|
|
115
|
+
"markdown (default) is best for reading; json includes the JSON Schema."
|
|
116
|
+
)
|
|
117
|
+
},
|
|
118
|
+
annotations: {
|
|
119
|
+
readOnlyHint: true,
|
|
120
|
+
idempotentHint: true,
|
|
121
|
+
openWorldHint: false
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
async ({ name, format }) => {
|
|
125
|
+
try {
|
|
126
|
+
if (format === "json")
|
|
127
|
+
return json({ ...describe(name), jsonSchema: jsonSchemaFor(name) });
|
|
128
|
+
return text(styleCard(name));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
return error(e.message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
server.registerTool(
|
|
135
|
+
"list_palettes",
|
|
136
|
+
{
|
|
137
|
+
title: "List palettes",
|
|
138
|
+
description: "Built-in curated colour palettes with their hex colours and derived mood tags (dark, pastel, vivid, warm, cool\u2026). Pass the id as palette.palette to generate_background.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
mood: z.string().optional().describe('Filter by a mood tag, e.g. "dark" or "pastel".')
|
|
141
|
+
},
|
|
142
|
+
annotations: {
|
|
143
|
+
readOnlyHint: true,
|
|
144
|
+
idempotentHint: true,
|
|
145
|
+
openWorldHint: false
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
async ({ mood }) => {
|
|
149
|
+
let items = listPalettes();
|
|
150
|
+
if (mood)
|
|
151
|
+
items = items.filter((p) => p.mood.includes(mood.toLowerCase()));
|
|
152
|
+
return json({ count: items.length, palettes: items });
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
if (hosted?.listBrandKits) {
|
|
156
|
+
server.registerTool(
|
|
157
|
+
"list_brand_kits",
|
|
158
|
+
{
|
|
159
|
+
title: "List brand kits",
|
|
160
|
+
description: "Saved brand palettes on this account. Pass a kit's slug as `brand` to generate_background to render on-brand without repeating hex colours.",
|
|
161
|
+
inputSchema: {},
|
|
162
|
+
annotations: {
|
|
163
|
+
readOnlyHint: true,
|
|
164
|
+
idempotentHint: true,
|
|
165
|
+
openWorldHint: false
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
async () => json({ brandKits: await hosted.listBrandKits() })
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
server.registerTool(
|
|
172
|
+
"generate_background",
|
|
173
|
+
{
|
|
174
|
+
title: "Generate a background",
|
|
175
|
+
description: [
|
|
176
|
+
"Render a background as SVG or PNG. Deterministic: the same structure, options and seed always give the same image, so store the returned seed.",
|
|
177
|
+
hosted ? "Returns a permanent, cacheable image URL you can use directly in <img>/CSS, plus an inline PNG preview." : "Returns the image inline (PNG previews are visible to you); pass saveTo to also write the file to disk.",
|
|
178
|
+
"Call describe_background first to learn which shape options the structure honours."
|
|
179
|
+
].join(" "),
|
|
180
|
+
inputSchema: {
|
|
181
|
+
structure: z.string().describe('Structure name, e.g. "wave".'),
|
|
182
|
+
seed: z.string().optional().describe(
|
|
183
|
+
"Any string. Omit for a fresh random seed (returned in the result)."
|
|
184
|
+
),
|
|
185
|
+
width: z.number().int().min(1).max(8192).optional(),
|
|
186
|
+
height: z.number().int().min(1).max(8192).optional(),
|
|
187
|
+
palette: paletteShape.optional(),
|
|
188
|
+
shape: shapeShape.optional(),
|
|
189
|
+
format: z.enum(["svg", "png"]).optional().describe(
|
|
190
|
+
"png (default) returns a viewable image; svg returns the vector source."
|
|
191
|
+
),
|
|
192
|
+
pngWidth: z.number().int().min(16).max(8192).optional().describe(
|
|
193
|
+
"Raster width for PNG output/preview. Defaults to the render width, capped for inline previews."
|
|
194
|
+
),
|
|
195
|
+
...hosted?.listBrandKits ? {
|
|
196
|
+
brand: z.string().optional().describe(
|
|
197
|
+
"Slug of a saved brand kit (see list_brand_kits); its colours become the palette baseline."
|
|
198
|
+
)
|
|
199
|
+
} : {},
|
|
200
|
+
...hosted ? {} : {
|
|
201
|
+
saveTo: z.string().optional().describe(
|
|
202
|
+
"Local file path to write the result to (extension decides svg/png if format is omitted)."
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
annotations: {
|
|
207
|
+
readOnlyHint: true,
|
|
208
|
+
idempotentHint: true,
|
|
209
|
+
openWorldHint: false
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
async (args) => {
|
|
213
|
+
const input = { ...args };
|
|
214
|
+
const brandSlug = typeof input.brand === "string" ? input.brand : void 0;
|
|
215
|
+
delete input.brand;
|
|
216
|
+
if (brandSlug) {
|
|
217
|
+
const kit = (await hosted?.listBrandKits?.())?.find(
|
|
218
|
+
(k) => k.slug === brandSlug
|
|
219
|
+
);
|
|
220
|
+
if (!kit)
|
|
221
|
+
return error(
|
|
222
|
+
`Unknown brand kit "${brandSlug}". Call list_brand_kits to see the available ones.`
|
|
223
|
+
);
|
|
224
|
+
const basePalette = { colors: kit.colors };
|
|
225
|
+
if (kit.backgroundColor)
|
|
226
|
+
basePalette.backgroundColor = kit.backgroundColor;
|
|
227
|
+
input.palette = {
|
|
228
|
+
...basePalette,
|
|
229
|
+
...input.palette ?? {}
|
|
230
|
+
};
|
|
231
|
+
if (!input.structure && kit.defaultStructure)
|
|
232
|
+
input.structure = kit.defaultStructure;
|
|
233
|
+
}
|
|
234
|
+
let normalized;
|
|
235
|
+
try {
|
|
236
|
+
normalized = normalizeRequest(input);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return error(e.message);
|
|
239
|
+
}
|
|
240
|
+
const { request, warnings } = normalized;
|
|
241
|
+
const saveTo = hosted ? void 0 : args.saveTo;
|
|
242
|
+
const format = args.format ?? (saveTo?.toLowerCase().endsWith(".svg") ? "svg" : "png");
|
|
243
|
+
try {
|
|
244
|
+
await hosted?.beforeRender?.(request);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return error(e.message);
|
|
247
|
+
}
|
|
248
|
+
const result = await renderSvg(request);
|
|
249
|
+
await hosted?.onRender?.(result, format);
|
|
250
|
+
const content = [];
|
|
251
|
+
const meta = {
|
|
252
|
+
structure: result.structure,
|
|
253
|
+
seed: result.seed,
|
|
254
|
+
width: result.width,
|
|
255
|
+
height: result.height,
|
|
256
|
+
format,
|
|
257
|
+
svgBytes: result.svg.length,
|
|
258
|
+
renderMs: result.durationMs,
|
|
259
|
+
// Echo the resolved options so the caller can reproduce or tweak.
|
|
260
|
+
request: { ...request, seed: result.seed },
|
|
261
|
+
...warnings.length ? { warnings } : {}
|
|
262
|
+
};
|
|
263
|
+
if (hosted) {
|
|
264
|
+
meta.url = renderUrl(
|
|
265
|
+
hosted.publicBaseUrl,
|
|
266
|
+
{ ...request, seed: result.seed },
|
|
267
|
+
format
|
|
268
|
+
);
|
|
269
|
+
meta.svgUrl = renderUrl(
|
|
270
|
+
hosted.publicBaseUrl,
|
|
271
|
+
{ ...request, seed: result.seed },
|
|
272
|
+
"svg"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (saveTo) {
|
|
276
|
+
const path = resolve(saveTo);
|
|
277
|
+
await mkdir(dirname(path), { recursive: true });
|
|
278
|
+
const bytes = format === "png" ? await renderPng(result.svg, { width: args.pngWidth ?? result.width }) : Buffer.from(result.svg, "utf8");
|
|
279
|
+
await writeFile(path, bytes);
|
|
280
|
+
meta.savedTo = path;
|
|
281
|
+
meta.bytes = bytes.length;
|
|
282
|
+
}
|
|
283
|
+
if (format === "svg") {
|
|
284
|
+
if (result.svg.length <= maxInline && !saveTo) {
|
|
285
|
+
content.push({
|
|
286
|
+
type: "resource",
|
|
287
|
+
resource: {
|
|
288
|
+
uri: `creatica://render/${result.structure}/${result.seed}.svg`,
|
|
289
|
+
mimeType: "image/svg+xml",
|
|
290
|
+
text: result.svg
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
} else if (!saveTo && !hosted) {
|
|
294
|
+
meta.note = `SVG is ${result.svg.length} bytes; pass saveTo to write it to disk instead of returning it inline.`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const previewWidth = Math.min(args.pngWidth ?? 800, 1200);
|
|
298
|
+
const png = await renderPng(result.svg, { width: previewWidth });
|
|
299
|
+
if (png.length <= maxInline) {
|
|
300
|
+
content.push({
|
|
301
|
+
type: "image",
|
|
302
|
+
data: png.toString("base64"),
|
|
303
|
+
mimeType: "image/png"
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
const small = await renderPng(result.svg, { width: 480 });
|
|
307
|
+
content.push({
|
|
308
|
+
type: "image",
|
|
309
|
+
data: small.toString("base64"),
|
|
310
|
+
mimeType: "image/png"
|
|
311
|
+
});
|
|
312
|
+
meta.note = `Preview downscaled to 480px (full PNG would be ${png.length} bytes).`;
|
|
313
|
+
}
|
|
314
|
+
content.unshift({ type: "text", text: JSON.stringify(meta, null, 2) });
|
|
315
|
+
return { content };
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
return server;
|
|
319
|
+
}
|
|
320
|
+
function renderUrl(baseUrl, req, format) {
|
|
321
|
+
const u = new URL(
|
|
322
|
+
`/v1/render/${encodeURIComponent(req.structure)}.${format}`,
|
|
323
|
+
baseUrl
|
|
324
|
+
);
|
|
325
|
+
if (req.seed !== void 0) u.searchParams.set("seed", String(req.seed));
|
|
326
|
+
if (req.width) u.searchParams.set("width", String(req.width));
|
|
327
|
+
if (req.height) u.searchParams.set("height", String(req.height));
|
|
328
|
+
for (const [k, v] of Object.entries(req.palette ?? {})) {
|
|
329
|
+
if (v === void 0) continue;
|
|
330
|
+
u.searchParams.set(k, Array.isArray(v) ? v.join(",") : String(v));
|
|
331
|
+
}
|
|
332
|
+
for (const [k, v] of Object.entries(req.shape ?? {})) {
|
|
333
|
+
if (v !== void 0) u.searchParams.set(k, String(v));
|
|
334
|
+
}
|
|
335
|
+
return u.toString();
|
|
336
|
+
}
|
|
337
|
+
function json(value) {
|
|
338
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
339
|
+
}
|
|
340
|
+
function text(value) {
|
|
341
|
+
return { content: [{ type: "text", text: value }] };
|
|
342
|
+
}
|
|
343
|
+
function error(message) {
|
|
344
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
createMcpServer,
|
|
348
|
+
renderUrl
|
|
349
|
+
};
|
|
350
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import {\n ENGINE_VERSION,\n renderPng,\n renderSvg,\n type RenderRequest,\n type RenderResult,\n} from '@creatica/engine';\nimport {\n catalog,\n describe,\n jsonSchemaFor,\n listPalettes,\n normalizeRequest,\n styleCard,\n suggest,\n} from '@creatica/schema';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { z } from 'zod';\n\nexport interface BrandKitSummary {\n slug: string;\n name: string;\n colors: string[];\n backgroundColor?: string;\n defaultStructure?: string;\n}\n\nexport interface HostedOptions {\n /** Public origin of the API, e.g. https://api.creatica.app */\n publicBaseUrl: string;\n /** Brand kits of the calling account; enables `brand` on generate and list_brand_kits. */\n listBrandKits?: () => Promise<BrandKitSummary[]>;\n /**\n * Called before rendering. Throw to refuse (e.g. quota exhausted); the\n * message is returned to the model as a tool error, so discovery tools keep\n * working and the model can explain what to do next.\n */\n beforeRender?: (request: RenderRequest) => void | Promise<void>;\n /** Called after a successful render so the host can meter usage. */\n onRender?: (\n result: RenderResult,\n format: 'svg' | 'png',\n ) => void | Promise<void>;\n}\n\nexport interface McpServerOptions {\n /**\n * Local mode (default) renders in-process and can write files to disk.\n * Hosted mode additionally returns permanent, cacheable image URLs and\n * never touches the filesystem.\n */\n hosted?: HostedOptions;\n /** Inline PNG previews above this many bytes are downscaled. */\n maxInlineBytes?: number;\n}\n\nconst hexColor = z\n .string()\n .regex(\n /^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,\n 'hex colour like #1e90ff',\n );\n\nconst paletteShape = z\n .object({\n colors: z\n .array(hexColor)\n .min(1)\n .max(12)\n .optional()\n .describe(\n 'Explicit hex colours to draw from (best for brand matching). Overrides palette/scheme.',\n ),\n palette: z\n .string()\n .optional()\n .describe(\n 'Id of a built-in palette from list_palettes, e.g. \"underTheSea\".',\n ),\n primaryColor: hexColor.optional(),\n secondaryColor: hexColor.optional(),\n backgroundColor: hexColor.optional(),\n gradientColor: z\n .boolean()\n .optional()\n .describe('Gradient fills instead of flat colour.'),\n useMultiColor: z\n .boolean()\n .optional()\n .describe('false = everything in primaryColor.'),\n colorSchemeType: z\n .enum(['Mono', 'Tetrade', 'Analogic'])\n .optional()\n .describe('Derive a palette from primaryColor.'),\n opaqueColors: z.boolean().optional(),\n })\n .strict();\n\n// Shape options are a loose record: which keys apply depends on the structure\n// (see describe_background). normalizeRequest() validates against that.\nconst shapeShape = z\n .record(z.string(), z.union([z.number(), z.boolean(), z.string()]))\n .describe(\n 'Structure-specific options; call describe_background for the exact keys, ranges and what they do.',\n );\n\nexport function createMcpServer(options: McpServerOptions = {}): McpServer {\n const { hosted } = options;\n const maxInline = options.maxInlineBytes ?? 600_000;\n\n const server = new McpServer(\n { name: 'creatica-backgrounds', version: ENGINE_VERSION },\n {\n instructions: [\n 'Creatica generates abstract, procedural SVG/PNG backgrounds (waves, blobs, bauhaus grids, topo lines, aurora glows…) for websites, slides, social cards and apps.',\n 'Workflow: suggest_backgrounds (or list_backgrounds) with the brief → describe_background for the chosen one → generate_background with a seed. Every render is deterministic: same structure + params + seed = identical image, so keep the seed to reproduce or make on-brand variants.',\n 'For brand colours pass palette.colors as hex. For a curated look pass palette.palette from list_palettes. Prefer PNG when you need a preview; SVG for the final asset.',\n ].join('\\n'),\n },\n );\n\n server.registerTool(\n 'list_backgrounds',\n {\n title: 'List backgrounds',\n description:\n 'Browse the catalog of background structures. Optionally filter by a free-text query, tag or mood word. Returns name, one-line summary, mood, use cases and a preview URL for each.',\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('Free text, e.g. \"calm waves\" or \"geometric poster\".'),\n mood: z\n .string()\n .optional()\n .describe('A mood word, e.g. dark, playful, minimal, retro.'),\n tag: z.string().optional(),\n limit: z.number().int().min(1).max(100).optional(),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ query, mood, tag, limit }) => {\n let items = catalog();\n if (mood)\n items = items.filter((c) =>\n c.mood.some((m) => m.toLowerCase().includes(mood.toLowerCase())),\n );\n if (tag)\n items = items.filter((c) =>\n c.tags.some((t) => t.toLowerCase().includes(tag.toLowerCase())),\n );\n if (query) {\n const ranked = suggest(query, { limit: 100 }).map((s) => s.name);\n items = items\n .filter((c) => ranked.includes(c.name))\n .sort((a, b) => ranked.indexOf(a.name) - ranked.indexOf(b.name));\n }\n items = items.slice(0, limit ?? 100);\n return json({ count: items.length, backgrounds: items });\n },\n );\n\n server.registerTool(\n 'suggest_backgrounds',\n {\n title: 'Suggest backgrounds for a brief',\n description:\n 'Rank structures for a natural-language brief (\"dark aurora glow for an AI landing page\"). Returns why each matched, matching built-in palettes, and a ready-to-run example request for generate_background.',\n inputSchema: {\n brief: z.string().min(2),\n limit: z.number().int().min(1).max(20).optional(),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ brief, limit }) =>\n json({ suggestions: suggest(brief, { limit: limit ?? 5 }) }),\n );\n\n server.registerTool(\n 'describe_background',\n {\n title: 'Describe a background',\n description:\n 'Full style card for one structure: what it looks like, mood, where text can go, tips, and every parameter it honours with ranges, defaults and the visual effect of low/high values. Also returns the JSON Schema for a generate request.',\n inputSchema: {\n name: z\n .string()\n .describe('Structure name from list_backgrounds, e.g. \"wave\".'),\n format: z\n .enum(['markdown', 'json'])\n .optional()\n .describe(\n 'markdown (default) is best for reading; json includes the JSON Schema.',\n ),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ name, format }) => {\n try {\n if (format === 'json')\n return json({ ...describe(name), jsonSchema: jsonSchemaFor(name) });\n return text(styleCard(name));\n } catch (e) {\n return error((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n 'list_palettes',\n {\n title: 'List palettes',\n description:\n 'Built-in curated colour palettes with their hex colours and derived mood tags (dark, pastel, vivid, warm, cool…). Pass the id as palette.palette to generate_background.',\n inputSchema: {\n mood: z\n .string()\n .optional()\n .describe('Filter by a mood tag, e.g. \"dark\" or \"pastel\".'),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ mood }) => {\n let items = listPalettes();\n if (mood)\n items = items.filter((p) => p.mood.includes(mood.toLowerCase()));\n return json({ count: items.length, palettes: items });\n },\n );\n\n if (hosted?.listBrandKits) {\n server.registerTool(\n 'list_brand_kits',\n {\n title: 'List brand kits',\n description:\n \"Saved brand palettes on this account. Pass a kit's slug as `brand` to generate_background to render on-brand without repeating hex colours.\",\n inputSchema: {},\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => json({ brandKits: await hosted.listBrandKits!() }),\n );\n }\n\n server.registerTool(\n 'generate_background',\n {\n title: 'Generate a background',\n description: [\n 'Render a background as SVG or PNG. Deterministic: the same structure, options and seed always give the same image, so store the returned seed.',\n hosted\n ? 'Returns a permanent, cacheable image URL you can use directly in <img>/CSS, plus an inline PNG preview.'\n : 'Returns the image inline (PNG previews are visible to you); pass saveTo to also write the file to disk.',\n 'Call describe_background first to learn which shape options the structure honours.',\n ].join(' '),\n inputSchema: {\n structure: z.string().describe('Structure name, e.g. \"wave\".'),\n seed: z\n .string()\n .optional()\n .describe(\n 'Any string. Omit for a fresh random seed (returned in the result).',\n ),\n width: z.number().int().min(1).max(8192).optional(),\n height: z.number().int().min(1).max(8192).optional(),\n palette: paletteShape.optional(),\n shape: shapeShape.optional(),\n format: z\n .enum(['svg', 'png'])\n .optional()\n .describe(\n 'png (default) returns a viewable image; svg returns the vector source.',\n ),\n pngWidth: z\n .number()\n .int()\n .min(16)\n .max(8192)\n .optional()\n .describe(\n 'Raster width for PNG output/preview. Defaults to the render width, capped for inline previews.',\n ),\n ...(hosted?.listBrandKits\n ? {\n brand: z\n .string()\n .optional()\n .describe(\n 'Slug of a saved brand kit (see list_brand_kits); its colours become the palette baseline.',\n ),\n }\n : {}),\n ...(hosted\n ? {}\n : {\n saveTo: z\n .string()\n .optional()\n .describe(\n 'Local file path to write the result to (extension decides svg/png if format is omitted).',\n ),\n }),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async (args) => {\n const input = { ...(args as Record<string, unknown>) };\n const brandSlug =\n typeof input.brand === 'string' ? input.brand : undefined;\n delete input.brand;\n if (brandSlug) {\n const kit = (await hosted?.listBrandKits?.())?.find(\n (k) => k.slug === brandSlug,\n );\n if (!kit)\n return error(\n `Unknown brand kit \"${brandSlug}\". Call list_brand_kits to see the available ones.`,\n );\n const basePalette: Record<string, unknown> = { colors: kit.colors };\n if (kit.backgroundColor)\n basePalette.backgroundColor = kit.backgroundColor;\n input.palette = {\n ...basePalette,\n ...((input.palette as Record<string, unknown>) ?? {}),\n };\n if (!input.structure && kit.defaultStructure)\n input.structure = kit.defaultStructure;\n }\n let normalized;\n try {\n normalized = normalizeRequest(input);\n } catch (e) {\n return error((e as Error).message);\n }\n const { request, warnings } = normalized;\n const saveTo = hosted ? undefined : (args as { saveTo?: string }).saveTo;\n const format: 'svg' | 'png' =\n (args.format as 'svg' | 'png' | undefined) ??\n (saveTo?.toLowerCase().endsWith('.svg') ? 'svg' : 'png');\n\n try {\n await hosted?.beforeRender?.(request);\n } catch (e) {\n return error((e as Error).message);\n }\n const result = await renderSvg(request);\n await hosted?.onRender?.(result, format);\n\n const content: CallToolResult['content'] = [];\n const meta: Record<string, unknown> = {\n structure: result.structure,\n seed: result.seed,\n width: result.width,\n height: result.height,\n format,\n svgBytes: result.svg.length,\n renderMs: result.durationMs,\n // Echo the resolved options so the caller can reproduce or tweak.\n request: { ...request, seed: result.seed },\n ...(warnings.length ? { warnings } : {}),\n };\n\n if (hosted) {\n meta.url = renderUrl(\n hosted.publicBaseUrl,\n { ...request, seed: result.seed },\n format,\n );\n meta.svgUrl = renderUrl(\n hosted.publicBaseUrl,\n { ...request, seed: result.seed },\n 'svg',\n );\n }\n\n if (saveTo) {\n const path = resolve(saveTo);\n await mkdir(dirname(path), { recursive: true });\n const bytes =\n format === 'png'\n ? await renderPng(result.svg, { width: args.pngWidth ?? result.width })\n : Buffer.from(result.svg, 'utf8');\n await writeFile(path, bytes);\n meta.savedTo = path;\n meta.bytes = bytes.length;\n }\n\n if (format === 'svg') {\n if (result.svg.length <= maxInline && !saveTo) {\n content.push({\n type: 'resource',\n resource: {\n uri: `creatica://render/${result.structure}/${result.seed}.svg`,\n mimeType: 'image/svg+xml',\n text: result.svg,\n },\n });\n } else if (!saveTo && !hosted) {\n meta.note = `SVG is ${result.svg.length} bytes; pass saveTo to write it to disk instead of returning it inline.`;\n }\n }\n\n // Always attach a PNG preview so the model can see what it made. Keep\n // it modest: previews are for judging composition, not final delivery.\n const previewWidth = Math.min(args.pngWidth ?? 800, 1200);\n const png = await renderPng(result.svg, { width: previewWidth });\n if (png.length <= maxInline) {\n content.push({\n type: 'image',\n data: png.toString('base64'),\n mimeType: 'image/png',\n });\n } else {\n const small = await renderPng(result.svg, { width: 480 });\n content.push({\n type: 'image',\n data: small.toString('base64'),\n mimeType: 'image/png',\n });\n meta.note = `Preview downscaled to 480px (full PNG would be ${png.length} bytes).`;\n }\n\n content.unshift({ type: 'text', text: JSON.stringify(meta, null, 2) });\n return { content };\n },\n );\n\n return server;\n}\n\nexport function renderUrl(\n baseUrl: string,\n req: RenderRequest,\n format: 'svg' | 'png',\n): string {\n const u = new URL(\n `/v1/render/${encodeURIComponent(req.structure)}.${format}`,\n baseUrl,\n );\n if (req.seed !== undefined) u.searchParams.set('seed', String(req.seed));\n if (req.width) u.searchParams.set('width', String(req.width));\n if (req.height) u.searchParams.set('height', String(req.height));\n for (const [k, v] of Object.entries(req.palette ?? {})) {\n if (v === undefined) continue;\n u.searchParams.set(k, Array.isArray(v) ? v.join(',') : String(v));\n }\n for (const [k, v] of Object.entries(req.shape ?? {})) {\n if (v !== undefined) u.searchParams.set(k, String(v));\n }\n return u.toString();\n}\n\nfunction json(value: unknown): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };\n}\nfunction text(value: string): CallToolResult {\n return { content: [{ type: 'text', text: value }] };\n}\nfunction error(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n"],"mappings":"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","names":[]}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/stdio.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import {
|
|
8
|
+
ENGINE_VERSION,
|
|
9
|
+
renderPng,
|
|
10
|
+
renderSvg
|
|
11
|
+
} from "@creatica/engine";
|
|
12
|
+
import {
|
|
13
|
+
catalog,
|
|
14
|
+
describe,
|
|
15
|
+
jsonSchemaFor,
|
|
16
|
+
listPalettes,
|
|
17
|
+
normalizeRequest,
|
|
18
|
+
styleCard,
|
|
19
|
+
suggest
|
|
20
|
+
} from "@creatica/schema";
|
|
21
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
23
|
+
import { dirname, resolve } from "path";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
var hexColor = z.string().regex(
|
|
26
|
+
/^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,
|
|
27
|
+
"hex colour like #1e90ff"
|
|
28
|
+
);
|
|
29
|
+
var paletteShape = z.object({
|
|
30
|
+
colors: z.array(hexColor).min(1).max(12).optional().describe(
|
|
31
|
+
"Explicit hex colours to draw from (best for brand matching). Overrides palette/scheme."
|
|
32
|
+
),
|
|
33
|
+
palette: z.string().optional().describe(
|
|
34
|
+
'Id of a built-in palette from list_palettes, e.g. "underTheSea".'
|
|
35
|
+
),
|
|
36
|
+
primaryColor: hexColor.optional(),
|
|
37
|
+
secondaryColor: hexColor.optional(),
|
|
38
|
+
backgroundColor: hexColor.optional(),
|
|
39
|
+
gradientColor: z.boolean().optional().describe("Gradient fills instead of flat colour."),
|
|
40
|
+
useMultiColor: z.boolean().optional().describe("false = everything in primaryColor."),
|
|
41
|
+
colorSchemeType: z.enum(["Mono", "Tetrade", "Analogic"]).optional().describe("Derive a palette from primaryColor."),
|
|
42
|
+
opaqueColors: z.boolean().optional()
|
|
43
|
+
}).strict();
|
|
44
|
+
var shapeShape = z.record(z.string(), z.union([z.number(), z.boolean(), z.string()])).describe(
|
|
45
|
+
"Structure-specific options; call describe_background for the exact keys, ranges and what they do."
|
|
46
|
+
);
|
|
47
|
+
function createMcpServer(options = {}) {
|
|
48
|
+
const { hosted } = options;
|
|
49
|
+
const maxInline = options.maxInlineBytes ?? 6e5;
|
|
50
|
+
const server2 = new McpServer(
|
|
51
|
+
{ name: "creatica-backgrounds", version: ENGINE_VERSION },
|
|
52
|
+
{
|
|
53
|
+
instructions: [
|
|
54
|
+
"Creatica generates abstract, procedural SVG/PNG backgrounds (waves, blobs, bauhaus grids, topo lines, aurora glows\u2026) for websites, slides, social cards and apps.",
|
|
55
|
+
"Workflow: suggest_backgrounds (or list_backgrounds) with the brief \u2192 describe_background for the chosen one \u2192 generate_background with a seed. Every render is deterministic: same structure + params + seed = identical image, so keep the seed to reproduce or make on-brand variants.",
|
|
56
|
+
"For brand colours pass palette.colors as hex. For a curated look pass palette.palette from list_palettes. Prefer PNG when you need a preview; SVG for the final asset."
|
|
57
|
+
].join("\n")
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
server2.registerTool(
|
|
61
|
+
"list_backgrounds",
|
|
62
|
+
{
|
|
63
|
+
title: "List backgrounds",
|
|
64
|
+
description: "Browse the catalog of background structures. Optionally filter by a free-text query, tag or mood word. Returns name, one-line summary, mood, use cases and a preview URL for each.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
query: z.string().optional().describe('Free text, e.g. "calm waves" or "geometric poster".'),
|
|
67
|
+
mood: z.string().optional().describe("A mood word, e.g. dark, playful, minimal, retro."),
|
|
68
|
+
tag: z.string().optional(),
|
|
69
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
70
|
+
},
|
|
71
|
+
annotations: {
|
|
72
|
+
readOnlyHint: true,
|
|
73
|
+
idempotentHint: true,
|
|
74
|
+
openWorldHint: false
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async ({ query, mood, tag, limit }) => {
|
|
78
|
+
let items = catalog();
|
|
79
|
+
if (mood)
|
|
80
|
+
items = items.filter(
|
|
81
|
+
(c) => c.mood.some((m) => m.toLowerCase().includes(mood.toLowerCase()))
|
|
82
|
+
);
|
|
83
|
+
if (tag)
|
|
84
|
+
items = items.filter(
|
|
85
|
+
(c) => c.tags.some((t) => t.toLowerCase().includes(tag.toLowerCase()))
|
|
86
|
+
);
|
|
87
|
+
if (query) {
|
|
88
|
+
const ranked = suggest(query, { limit: 100 }).map((s) => s.name);
|
|
89
|
+
items = items.filter((c) => ranked.includes(c.name)).sort((a, b) => ranked.indexOf(a.name) - ranked.indexOf(b.name));
|
|
90
|
+
}
|
|
91
|
+
items = items.slice(0, limit ?? 100);
|
|
92
|
+
return json({ count: items.length, backgrounds: items });
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
server2.registerTool(
|
|
96
|
+
"suggest_backgrounds",
|
|
97
|
+
{
|
|
98
|
+
title: "Suggest backgrounds for a brief",
|
|
99
|
+
description: 'Rank structures for a natural-language brief ("dark aurora glow for an AI landing page"). Returns why each matched, matching built-in palettes, and a ready-to-run example request for generate_background.',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
brief: z.string().min(2),
|
|
102
|
+
limit: z.number().int().min(1).max(20).optional()
|
|
103
|
+
},
|
|
104
|
+
annotations: {
|
|
105
|
+
readOnlyHint: true,
|
|
106
|
+
idempotentHint: true,
|
|
107
|
+
openWorldHint: false
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async ({ brief, limit }) => json({ suggestions: suggest(brief, { limit: limit ?? 5 }) })
|
|
111
|
+
);
|
|
112
|
+
server2.registerTool(
|
|
113
|
+
"describe_background",
|
|
114
|
+
{
|
|
115
|
+
title: "Describe a background",
|
|
116
|
+
description: "Full style card for one structure: what it looks like, mood, where text can go, tips, and every parameter it honours with ranges, defaults and the visual effect of low/high values. Also returns the JSON Schema for a generate request.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
name: z.string().describe('Structure name from list_backgrounds, e.g. "wave".'),
|
|
119
|
+
format: z.enum(["markdown", "json"]).optional().describe(
|
|
120
|
+
"markdown (default) is best for reading; json includes the JSON Schema."
|
|
121
|
+
)
|
|
122
|
+
},
|
|
123
|
+
annotations: {
|
|
124
|
+
readOnlyHint: true,
|
|
125
|
+
idempotentHint: true,
|
|
126
|
+
openWorldHint: false
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
async ({ name, format }) => {
|
|
130
|
+
try {
|
|
131
|
+
if (format === "json")
|
|
132
|
+
return json({ ...describe(name), jsonSchema: jsonSchemaFor(name) });
|
|
133
|
+
return text(styleCard(name));
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return error(e.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
server2.registerTool(
|
|
140
|
+
"list_palettes",
|
|
141
|
+
{
|
|
142
|
+
title: "List palettes",
|
|
143
|
+
description: "Built-in curated colour palettes with their hex colours and derived mood tags (dark, pastel, vivid, warm, cool\u2026). Pass the id as palette.palette to generate_background.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
mood: z.string().optional().describe('Filter by a mood tag, e.g. "dark" or "pastel".')
|
|
146
|
+
},
|
|
147
|
+
annotations: {
|
|
148
|
+
readOnlyHint: true,
|
|
149
|
+
idempotentHint: true,
|
|
150
|
+
openWorldHint: false
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
async ({ mood }) => {
|
|
154
|
+
let items = listPalettes();
|
|
155
|
+
if (mood)
|
|
156
|
+
items = items.filter((p) => p.mood.includes(mood.toLowerCase()));
|
|
157
|
+
return json({ count: items.length, palettes: items });
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
if (hosted?.listBrandKits) {
|
|
161
|
+
server2.registerTool(
|
|
162
|
+
"list_brand_kits",
|
|
163
|
+
{
|
|
164
|
+
title: "List brand kits",
|
|
165
|
+
description: "Saved brand palettes on this account. Pass a kit's slug as `brand` to generate_background to render on-brand without repeating hex colours.",
|
|
166
|
+
inputSchema: {},
|
|
167
|
+
annotations: {
|
|
168
|
+
readOnlyHint: true,
|
|
169
|
+
idempotentHint: true,
|
|
170
|
+
openWorldHint: false
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
async () => json({ brandKits: await hosted.listBrandKits() })
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
server2.registerTool(
|
|
177
|
+
"generate_background",
|
|
178
|
+
{
|
|
179
|
+
title: "Generate a background",
|
|
180
|
+
description: [
|
|
181
|
+
"Render a background as SVG or PNG. Deterministic: the same structure, options and seed always give the same image, so store the returned seed.",
|
|
182
|
+
hosted ? "Returns a permanent, cacheable image URL you can use directly in <img>/CSS, plus an inline PNG preview." : "Returns the image inline (PNG previews are visible to you); pass saveTo to also write the file to disk.",
|
|
183
|
+
"Call describe_background first to learn which shape options the structure honours."
|
|
184
|
+
].join(" "),
|
|
185
|
+
inputSchema: {
|
|
186
|
+
structure: z.string().describe('Structure name, e.g. "wave".'),
|
|
187
|
+
seed: z.string().optional().describe(
|
|
188
|
+
"Any string. Omit for a fresh random seed (returned in the result)."
|
|
189
|
+
),
|
|
190
|
+
width: z.number().int().min(1).max(8192).optional(),
|
|
191
|
+
height: z.number().int().min(1).max(8192).optional(),
|
|
192
|
+
palette: paletteShape.optional(),
|
|
193
|
+
shape: shapeShape.optional(),
|
|
194
|
+
format: z.enum(["svg", "png"]).optional().describe(
|
|
195
|
+
"png (default) returns a viewable image; svg returns the vector source."
|
|
196
|
+
),
|
|
197
|
+
pngWidth: z.number().int().min(16).max(8192).optional().describe(
|
|
198
|
+
"Raster width for PNG output/preview. Defaults to the render width, capped for inline previews."
|
|
199
|
+
),
|
|
200
|
+
...hosted?.listBrandKits ? {
|
|
201
|
+
brand: z.string().optional().describe(
|
|
202
|
+
"Slug of a saved brand kit (see list_brand_kits); its colours become the palette baseline."
|
|
203
|
+
)
|
|
204
|
+
} : {},
|
|
205
|
+
...hosted ? {} : {
|
|
206
|
+
saveTo: z.string().optional().describe(
|
|
207
|
+
"Local file path to write the result to (extension decides svg/png if format is omitted)."
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
annotations: {
|
|
212
|
+
readOnlyHint: true,
|
|
213
|
+
idempotentHint: true,
|
|
214
|
+
openWorldHint: false
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
async (args) => {
|
|
218
|
+
const input = { ...args };
|
|
219
|
+
const brandSlug = typeof input.brand === "string" ? input.brand : void 0;
|
|
220
|
+
delete input.brand;
|
|
221
|
+
if (brandSlug) {
|
|
222
|
+
const kit = (await hosted?.listBrandKits?.())?.find(
|
|
223
|
+
(k) => k.slug === brandSlug
|
|
224
|
+
);
|
|
225
|
+
if (!kit)
|
|
226
|
+
return error(
|
|
227
|
+
`Unknown brand kit "${brandSlug}". Call list_brand_kits to see the available ones.`
|
|
228
|
+
);
|
|
229
|
+
const basePalette = { colors: kit.colors };
|
|
230
|
+
if (kit.backgroundColor)
|
|
231
|
+
basePalette.backgroundColor = kit.backgroundColor;
|
|
232
|
+
input.palette = {
|
|
233
|
+
...basePalette,
|
|
234
|
+
...input.palette ?? {}
|
|
235
|
+
};
|
|
236
|
+
if (!input.structure && kit.defaultStructure)
|
|
237
|
+
input.structure = kit.defaultStructure;
|
|
238
|
+
}
|
|
239
|
+
let normalized;
|
|
240
|
+
try {
|
|
241
|
+
normalized = normalizeRequest(input);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return error(e.message);
|
|
244
|
+
}
|
|
245
|
+
const { request, warnings } = normalized;
|
|
246
|
+
const saveTo = hosted ? void 0 : args.saveTo;
|
|
247
|
+
const format = args.format ?? (saveTo?.toLowerCase().endsWith(".svg") ? "svg" : "png");
|
|
248
|
+
try {
|
|
249
|
+
await hosted?.beforeRender?.(request);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
return error(e.message);
|
|
252
|
+
}
|
|
253
|
+
const result = await renderSvg(request);
|
|
254
|
+
await hosted?.onRender?.(result, format);
|
|
255
|
+
const content = [];
|
|
256
|
+
const meta = {
|
|
257
|
+
structure: result.structure,
|
|
258
|
+
seed: result.seed,
|
|
259
|
+
width: result.width,
|
|
260
|
+
height: result.height,
|
|
261
|
+
format,
|
|
262
|
+
svgBytes: result.svg.length,
|
|
263
|
+
renderMs: result.durationMs,
|
|
264
|
+
// Echo the resolved options so the caller can reproduce or tweak.
|
|
265
|
+
request: { ...request, seed: result.seed },
|
|
266
|
+
...warnings.length ? { warnings } : {}
|
|
267
|
+
};
|
|
268
|
+
if (hosted) {
|
|
269
|
+
meta.url = renderUrl(
|
|
270
|
+
hosted.publicBaseUrl,
|
|
271
|
+
{ ...request, seed: result.seed },
|
|
272
|
+
format
|
|
273
|
+
);
|
|
274
|
+
meta.svgUrl = renderUrl(
|
|
275
|
+
hosted.publicBaseUrl,
|
|
276
|
+
{ ...request, seed: result.seed },
|
|
277
|
+
"svg"
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (saveTo) {
|
|
281
|
+
const path = resolve(saveTo);
|
|
282
|
+
await mkdir(dirname(path), { recursive: true });
|
|
283
|
+
const bytes = format === "png" ? await renderPng(result.svg, { width: args.pngWidth ?? result.width }) : Buffer.from(result.svg, "utf8");
|
|
284
|
+
await writeFile(path, bytes);
|
|
285
|
+
meta.savedTo = path;
|
|
286
|
+
meta.bytes = bytes.length;
|
|
287
|
+
}
|
|
288
|
+
if (format === "svg") {
|
|
289
|
+
if (result.svg.length <= maxInline && !saveTo) {
|
|
290
|
+
content.push({
|
|
291
|
+
type: "resource",
|
|
292
|
+
resource: {
|
|
293
|
+
uri: `creatica://render/${result.structure}/${result.seed}.svg`,
|
|
294
|
+
mimeType: "image/svg+xml",
|
|
295
|
+
text: result.svg
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
} else if (!saveTo && !hosted) {
|
|
299
|
+
meta.note = `SVG is ${result.svg.length} bytes; pass saveTo to write it to disk instead of returning it inline.`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const previewWidth = Math.min(args.pngWidth ?? 800, 1200);
|
|
303
|
+
const png = await renderPng(result.svg, { width: previewWidth });
|
|
304
|
+
if (png.length <= maxInline) {
|
|
305
|
+
content.push({
|
|
306
|
+
type: "image",
|
|
307
|
+
data: png.toString("base64"),
|
|
308
|
+
mimeType: "image/png"
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
const small = await renderPng(result.svg, { width: 480 });
|
|
312
|
+
content.push({
|
|
313
|
+
type: "image",
|
|
314
|
+
data: small.toString("base64"),
|
|
315
|
+
mimeType: "image/png"
|
|
316
|
+
});
|
|
317
|
+
meta.note = `Preview downscaled to 480px (full PNG would be ${png.length} bytes).`;
|
|
318
|
+
}
|
|
319
|
+
content.unshift({ type: "text", text: JSON.stringify(meta, null, 2) });
|
|
320
|
+
return { content };
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
return server2;
|
|
324
|
+
}
|
|
325
|
+
function renderUrl(baseUrl, req, format) {
|
|
326
|
+
const u = new URL(
|
|
327
|
+
`/v1/render/${encodeURIComponent(req.structure)}.${format}`,
|
|
328
|
+
baseUrl
|
|
329
|
+
);
|
|
330
|
+
if (req.seed !== void 0) u.searchParams.set("seed", String(req.seed));
|
|
331
|
+
if (req.width) u.searchParams.set("width", String(req.width));
|
|
332
|
+
if (req.height) u.searchParams.set("height", String(req.height));
|
|
333
|
+
for (const [k, v] of Object.entries(req.palette ?? {})) {
|
|
334
|
+
if (v === void 0) continue;
|
|
335
|
+
u.searchParams.set(k, Array.isArray(v) ? v.join(",") : String(v));
|
|
336
|
+
}
|
|
337
|
+
for (const [k, v] of Object.entries(req.shape ?? {})) {
|
|
338
|
+
if (v !== void 0) u.searchParams.set(k, String(v));
|
|
339
|
+
}
|
|
340
|
+
return u.toString();
|
|
341
|
+
}
|
|
342
|
+
function json(value) {
|
|
343
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
344
|
+
}
|
|
345
|
+
function text(value) {
|
|
346
|
+
return { content: [{ type: "text", text: value }] };
|
|
347
|
+
}
|
|
348
|
+
function error(message) {
|
|
349
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/stdio.ts
|
|
353
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
354
|
+
process.stdout.write(
|
|
355
|
+
[
|
|
356
|
+
"creatica-mcp \u2014 Model Context Protocol server for Creatica backgrounds (stdio).",
|
|
357
|
+
"",
|
|
358
|
+
"Tools: list_backgrounds, suggest_backgrounds, describe_background, list_palettes, generate_background",
|
|
359
|
+
"",
|
|
360
|
+
"Claude Code: claude mcp add creatica -- npx -y @creatica/mcp",
|
|
361
|
+
'Claude Desktop: add {"mcpServers":{"creatica":{"command":"npx","args":["-y","@creatica/mcp"]}}}',
|
|
362
|
+
""
|
|
363
|
+
].join("\n")
|
|
364
|
+
);
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
var server = createMcpServer();
|
|
368
|
+
var transport = new StdioServerTransport();
|
|
369
|
+
await server.connect(transport);
|
|
370
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stdio.ts","../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n// Local MCP server over stdio. Runs the whole engine on this machine; no\n// network, no account. Add to Claude Desktop / Claude Code / Cursor as:\n// { \"command\": \"npx\", \"args\": [\"-y\", \"@creatica/mcp\"] }\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createMcpServer } from './server.js';\n\nif (process.argv.includes('--help') || process.argv.includes('-h')) {\n process.stdout.write(\n [\n 'creatica-mcp — Model Context Protocol server for Creatica backgrounds (stdio).',\n '',\n 'Tools: list_backgrounds, suggest_backgrounds, describe_background, list_palettes, generate_background',\n '',\n 'Claude Code: claude mcp add creatica -- npx -y @creatica/mcp',\n 'Claude Desktop: add {\"mcpServers\":{\"creatica\":{\"command\":\"npx\",\"args\":[\"-y\",\"@creatica/mcp\"]}}}',\n '',\n ].join('\\n'),\n );\n process.exit(0);\n}\n\nconst server = createMcpServer();\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import {\n ENGINE_VERSION,\n renderPng,\n renderSvg,\n type RenderRequest,\n type RenderResult,\n} from '@creatica/engine';\nimport {\n catalog,\n describe,\n jsonSchemaFor,\n listPalettes,\n normalizeRequest,\n styleCard,\n suggest,\n} from '@creatica/schema';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { z } from 'zod';\n\nexport interface BrandKitSummary {\n slug: string;\n name: string;\n colors: string[];\n backgroundColor?: string;\n defaultStructure?: string;\n}\n\nexport interface HostedOptions {\n /** Public origin of the API, e.g. https://api.creatica.app */\n publicBaseUrl: string;\n /** Brand kits of the calling account; enables `brand` on generate and list_brand_kits. */\n listBrandKits?: () => Promise<BrandKitSummary[]>;\n /**\n * Called before rendering. Throw to refuse (e.g. quota exhausted); the\n * message is returned to the model as a tool error, so discovery tools keep\n * working and the model can explain what to do next.\n */\n beforeRender?: (request: RenderRequest) => void | Promise<void>;\n /** Called after a successful render so the host can meter usage. */\n onRender?: (\n result: RenderResult,\n format: 'svg' | 'png',\n ) => void | Promise<void>;\n}\n\nexport interface McpServerOptions {\n /**\n * Local mode (default) renders in-process and can write files to disk.\n * Hosted mode additionally returns permanent, cacheable image URLs and\n * never touches the filesystem.\n */\n hosted?: HostedOptions;\n /** Inline PNG previews above this many bytes are downscaled. */\n maxInlineBytes?: number;\n}\n\nconst hexColor = z\n .string()\n .regex(\n /^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,\n 'hex colour like #1e90ff',\n );\n\nconst paletteShape = z\n .object({\n colors: z\n .array(hexColor)\n .min(1)\n .max(12)\n .optional()\n .describe(\n 'Explicit hex colours to draw from (best for brand matching). Overrides palette/scheme.',\n ),\n palette: z\n .string()\n .optional()\n .describe(\n 'Id of a built-in palette from list_palettes, e.g. \"underTheSea\".',\n ),\n primaryColor: hexColor.optional(),\n secondaryColor: hexColor.optional(),\n backgroundColor: hexColor.optional(),\n gradientColor: z\n .boolean()\n .optional()\n .describe('Gradient fills instead of flat colour.'),\n useMultiColor: z\n .boolean()\n .optional()\n .describe('false = everything in primaryColor.'),\n colorSchemeType: z\n .enum(['Mono', 'Tetrade', 'Analogic'])\n .optional()\n .describe('Derive a palette from primaryColor.'),\n opaqueColors: z.boolean().optional(),\n })\n .strict();\n\n// Shape options are a loose record: which keys apply depends on the structure\n// (see describe_background). normalizeRequest() validates against that.\nconst shapeShape = z\n .record(z.string(), z.union([z.number(), z.boolean(), z.string()]))\n .describe(\n 'Structure-specific options; call describe_background for the exact keys, ranges and what they do.',\n );\n\nexport function createMcpServer(options: McpServerOptions = {}): McpServer {\n const { hosted } = options;\n const maxInline = options.maxInlineBytes ?? 600_000;\n\n const server = new McpServer(\n { name: 'creatica-backgrounds', version: ENGINE_VERSION },\n {\n instructions: [\n 'Creatica generates abstract, procedural SVG/PNG backgrounds (waves, blobs, bauhaus grids, topo lines, aurora glows…) for websites, slides, social cards and apps.',\n 'Workflow: suggest_backgrounds (or list_backgrounds) with the brief → describe_background for the chosen one → generate_background with a seed. Every render is deterministic: same structure + params + seed = identical image, so keep the seed to reproduce or make on-brand variants.',\n 'For brand colours pass palette.colors as hex. For a curated look pass palette.palette from list_palettes. Prefer PNG when you need a preview; SVG for the final asset.',\n ].join('\\n'),\n },\n );\n\n server.registerTool(\n 'list_backgrounds',\n {\n title: 'List backgrounds',\n description:\n 'Browse the catalog of background structures. Optionally filter by a free-text query, tag or mood word. Returns name, one-line summary, mood, use cases and a preview URL for each.',\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('Free text, e.g. \"calm waves\" or \"geometric poster\".'),\n mood: z\n .string()\n .optional()\n .describe('A mood word, e.g. dark, playful, minimal, retro.'),\n tag: z.string().optional(),\n limit: z.number().int().min(1).max(100).optional(),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ query, mood, tag, limit }) => {\n let items = catalog();\n if (mood)\n items = items.filter((c) =>\n c.mood.some((m) => m.toLowerCase().includes(mood.toLowerCase())),\n );\n if (tag)\n items = items.filter((c) =>\n c.tags.some((t) => t.toLowerCase().includes(tag.toLowerCase())),\n );\n if (query) {\n const ranked = suggest(query, { limit: 100 }).map((s) => s.name);\n items = items\n .filter((c) => ranked.includes(c.name))\n .sort((a, b) => ranked.indexOf(a.name) - ranked.indexOf(b.name));\n }\n items = items.slice(0, limit ?? 100);\n return json({ count: items.length, backgrounds: items });\n },\n );\n\n server.registerTool(\n 'suggest_backgrounds',\n {\n title: 'Suggest backgrounds for a brief',\n description:\n 'Rank structures for a natural-language brief (\"dark aurora glow for an AI landing page\"). Returns why each matched, matching built-in palettes, and a ready-to-run example request for generate_background.',\n inputSchema: {\n brief: z.string().min(2),\n limit: z.number().int().min(1).max(20).optional(),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ brief, limit }) =>\n json({ suggestions: suggest(brief, { limit: limit ?? 5 }) }),\n );\n\n server.registerTool(\n 'describe_background',\n {\n title: 'Describe a background',\n description:\n 'Full style card for one structure: what it looks like, mood, where text can go, tips, and every parameter it honours with ranges, defaults and the visual effect of low/high values. Also returns the JSON Schema for a generate request.',\n inputSchema: {\n name: z\n .string()\n .describe('Structure name from list_backgrounds, e.g. \"wave\".'),\n format: z\n .enum(['markdown', 'json'])\n .optional()\n .describe(\n 'markdown (default) is best for reading; json includes the JSON Schema.',\n ),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ name, format }) => {\n try {\n if (format === 'json')\n return json({ ...describe(name), jsonSchema: jsonSchemaFor(name) });\n return text(styleCard(name));\n } catch (e) {\n return error((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n 'list_palettes',\n {\n title: 'List palettes',\n description:\n 'Built-in curated colour palettes with their hex colours and derived mood tags (dark, pastel, vivid, warm, cool…). Pass the id as palette.palette to generate_background.',\n inputSchema: {\n mood: z\n .string()\n .optional()\n .describe('Filter by a mood tag, e.g. \"dark\" or \"pastel\".'),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ mood }) => {\n let items = listPalettes();\n if (mood)\n items = items.filter((p) => p.mood.includes(mood.toLowerCase()));\n return json({ count: items.length, palettes: items });\n },\n );\n\n if (hosted?.listBrandKits) {\n server.registerTool(\n 'list_brand_kits',\n {\n title: 'List brand kits',\n description:\n \"Saved brand palettes on this account. Pass a kit's slug as `brand` to generate_background to render on-brand without repeating hex colours.\",\n inputSchema: {},\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => json({ brandKits: await hosted.listBrandKits!() }),\n );\n }\n\n server.registerTool(\n 'generate_background',\n {\n title: 'Generate a background',\n description: [\n 'Render a background as SVG or PNG. Deterministic: the same structure, options and seed always give the same image, so store the returned seed.',\n hosted\n ? 'Returns a permanent, cacheable image URL you can use directly in <img>/CSS, plus an inline PNG preview.'\n : 'Returns the image inline (PNG previews are visible to you); pass saveTo to also write the file to disk.',\n 'Call describe_background first to learn which shape options the structure honours.',\n ].join(' '),\n inputSchema: {\n structure: z.string().describe('Structure name, e.g. \"wave\".'),\n seed: z\n .string()\n .optional()\n .describe(\n 'Any string. Omit for a fresh random seed (returned in the result).',\n ),\n width: z.number().int().min(1).max(8192).optional(),\n height: z.number().int().min(1).max(8192).optional(),\n palette: paletteShape.optional(),\n shape: shapeShape.optional(),\n format: z\n .enum(['svg', 'png'])\n .optional()\n .describe(\n 'png (default) returns a viewable image; svg returns the vector source.',\n ),\n pngWidth: z\n .number()\n .int()\n .min(16)\n .max(8192)\n .optional()\n .describe(\n 'Raster width for PNG output/preview. Defaults to the render width, capped for inline previews.',\n ),\n ...(hosted?.listBrandKits\n ? {\n brand: z\n .string()\n .optional()\n .describe(\n 'Slug of a saved brand kit (see list_brand_kits); its colours become the palette baseline.',\n ),\n }\n : {}),\n ...(hosted\n ? {}\n : {\n saveTo: z\n .string()\n .optional()\n .describe(\n 'Local file path to write the result to (extension decides svg/png if format is omitted).',\n ),\n }),\n },\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async (args) => {\n const input = { ...(args as Record<string, unknown>) };\n const brandSlug =\n typeof input.brand === 'string' ? input.brand : undefined;\n delete input.brand;\n if (brandSlug) {\n const kit = (await hosted?.listBrandKits?.())?.find(\n (k) => k.slug === brandSlug,\n );\n if (!kit)\n return error(\n `Unknown brand kit \"${brandSlug}\". Call list_brand_kits to see the available ones.`,\n );\n const basePalette: Record<string, unknown> = { colors: kit.colors };\n if (kit.backgroundColor)\n basePalette.backgroundColor = kit.backgroundColor;\n input.palette = {\n ...basePalette,\n ...((input.palette as Record<string, unknown>) ?? {}),\n };\n if (!input.structure && kit.defaultStructure)\n input.structure = kit.defaultStructure;\n }\n let normalized;\n try {\n normalized = normalizeRequest(input);\n } catch (e) {\n return error((e as Error).message);\n }\n const { request, warnings } = normalized;\n const saveTo = hosted ? undefined : (args as { saveTo?: string }).saveTo;\n const format: 'svg' | 'png' =\n (args.format as 'svg' | 'png' | undefined) ??\n (saveTo?.toLowerCase().endsWith('.svg') ? 'svg' : 'png');\n\n try {\n await hosted?.beforeRender?.(request);\n } catch (e) {\n return error((e as Error).message);\n }\n const result = await renderSvg(request);\n await hosted?.onRender?.(result, format);\n\n const content: CallToolResult['content'] = [];\n const meta: Record<string, unknown> = {\n structure: result.structure,\n seed: result.seed,\n width: result.width,\n height: result.height,\n format,\n svgBytes: result.svg.length,\n renderMs: result.durationMs,\n // Echo the resolved options so the caller can reproduce or tweak.\n request: { ...request, seed: result.seed },\n ...(warnings.length ? { warnings } : {}),\n };\n\n if (hosted) {\n meta.url = renderUrl(\n hosted.publicBaseUrl,\n { ...request, seed: result.seed },\n format,\n );\n meta.svgUrl = renderUrl(\n hosted.publicBaseUrl,\n { ...request, seed: result.seed },\n 'svg',\n );\n }\n\n if (saveTo) {\n const path = resolve(saveTo);\n await mkdir(dirname(path), { recursive: true });\n const bytes =\n format === 'png'\n ? await renderPng(result.svg, { width: args.pngWidth ?? result.width })\n : Buffer.from(result.svg, 'utf8');\n await writeFile(path, bytes);\n meta.savedTo = path;\n meta.bytes = bytes.length;\n }\n\n if (format === 'svg') {\n if (result.svg.length <= maxInline && !saveTo) {\n content.push({\n type: 'resource',\n resource: {\n uri: `creatica://render/${result.structure}/${result.seed}.svg`,\n mimeType: 'image/svg+xml',\n text: result.svg,\n },\n });\n } else if (!saveTo && !hosted) {\n meta.note = `SVG is ${result.svg.length} bytes; pass saveTo to write it to disk instead of returning it inline.`;\n }\n }\n\n // Always attach a PNG preview so the model can see what it made. Keep\n // it modest: previews are for judging composition, not final delivery.\n const previewWidth = Math.min(args.pngWidth ?? 800, 1200);\n const png = await renderPng(result.svg, { width: previewWidth });\n if (png.length <= maxInline) {\n content.push({\n type: 'image',\n data: png.toString('base64'),\n mimeType: 'image/png',\n });\n } else {\n const small = await renderPng(result.svg, { width: 480 });\n content.push({\n type: 'image',\n data: small.toString('base64'),\n mimeType: 'image/png',\n });\n meta.note = `Preview downscaled to 480px (full PNG would be ${png.length} bytes).`;\n }\n\n content.unshift({ type: 'text', text: JSON.stringify(meta, null, 2) });\n return { content };\n },\n );\n\n return server;\n}\n\nexport function renderUrl(\n baseUrl: string,\n req: RenderRequest,\n format: 'svg' | 'png',\n): string {\n const u = new URL(\n `/v1/render/${encodeURIComponent(req.structure)}.${format}`,\n baseUrl,\n );\n if (req.seed !== undefined) u.searchParams.set('seed', String(req.seed));\n if (req.width) u.searchParams.set('width', String(req.width));\n if (req.height) u.searchParams.set('height', String(req.height));\n for (const [k, v] of Object.entries(req.palette ?? {})) {\n if (v === undefined) continue;\n u.searchParams.set(k, Array.isArray(v) ? v.join(',') : String(v));\n }\n for (const [k, v] of Object.entries(req.shape ?? {})) {\n if (v !== undefined) u.searchParams.set(k, String(v));\n }\n return u.toString();\n}\n\nfunction json(value: unknown): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };\n}\nfunction text(value: string): CallToolResult {\n return { content: [{ type: 'text', text: value }] };\n}\nfunction error(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n"],"mappings":"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","names":["server"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@creatica/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Creatica: let AI agents browse, describe and generate procedural SVG/PNG backgrounds on demand.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"creatica-mcp": "./dist/stdio.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"start": "node dist/stdio.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@creatica/engine": "0.1.0",
|
|
29
|
+
"@creatica/schema": "0.1.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
31
|
+
"zod": "^4.5.4"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/anup-a/web-backgrounds",
|
|
39
|
+
"directory": "packages/mcp"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://creatica.app",
|
|
42
|
+
"keywords": [
|
|
43
|
+
"svg",
|
|
44
|
+
"background",
|
|
45
|
+
"generator",
|
|
46
|
+
"mcp",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"ai",
|
|
49
|
+
"design",
|
|
50
|
+
"procedural"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20"
|
|
54
|
+
}
|
|
55
|
+
}
|