@appmockup/mcp 0.2.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 +37 -0
- package/dist/chunk-MGDARVK4.js +293 -0
- package/dist/chunk-MGDARVK4.js.map +1 -0
- package/dist/http.js +64 -0
- package/dist/http.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.js +12 -0
- package/dist/stdio.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @appmockup/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [AppMockup](https://github.com/yakupbulbul/AppMockup) — let any AI
|
|
4
|
+
assistant generate App Store / Play Store screenshot mockups. One tool set, two
|
|
5
|
+
transports.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# stdio (local assistants) — no install needed
|
|
9
|
+
npx -y @appmockup/mcp
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Transports
|
|
13
|
+
|
|
14
|
+
- **stdio** — bin `appmockup-mcp`. Reads screenshot **file paths** from disk (pass
|
|
15
|
+
`baseDir`).
|
|
16
|
+
- **HTTP** — bin `appmockup-mcp-http`. Standalone Streamable HTTP on `PORT`/`MCP_PATH`
|
|
17
|
+
(default `http://localhost:3333/mcp`, health at `/health`). Accepts screenshots as
|
|
18
|
+
**base64** (`images` map).
|
|
19
|
+
|
|
20
|
+
## Wiring into Claude Code
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude mcp add appmockup -- npx -y @appmockup/mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For Claude Desktop config and the full tool reference, see
|
|
27
|
+
[docs/mcp.md](../../docs/mcp.md).
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
`list_output_sizes`, `get_config_schema`, `init_config`, `list_templates`,
|
|
32
|
+
`apply_template`, `validate_config`, `preview_mockup`, `generate_mockups`
|
|
33
|
+
(+ `list_devices`, a deprecated alias of `list_output_sizes`).
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT © Yakup Bülbül
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/server.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
7
|
+
import {
|
|
8
|
+
mockupConfigSchema,
|
|
9
|
+
createStarterConfig,
|
|
10
|
+
STANDARD_OUTPUT_SIZES,
|
|
11
|
+
TEMPLATES,
|
|
12
|
+
getTemplate,
|
|
13
|
+
THEMES,
|
|
14
|
+
getTheme,
|
|
15
|
+
applyTheme
|
|
16
|
+
} from "@appmockup/core";
|
|
17
|
+
import {
|
|
18
|
+
renderOneToPng,
|
|
19
|
+
generate,
|
|
20
|
+
validateConfig,
|
|
21
|
+
decodeImageBuffer
|
|
22
|
+
} from "@appmockup/node-render";
|
|
23
|
+
var SERVER_VERSION = "0.3.0";
|
|
24
|
+
function parseConfigOrThrow(input) {
|
|
25
|
+
const result = mockupConfigSchema.safeParse(input);
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
const detail = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
28
|
+
throw new Error(`Invalid config: ${detail}`);
|
|
29
|
+
}
|
|
30
|
+
return result.data;
|
|
31
|
+
}
|
|
32
|
+
async function decodeImages(images) {
|
|
33
|
+
if (!images) return void 0;
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [id, value] of Object.entries(images)) {
|
|
36
|
+
const raw = value.replace(/^data:image\/[a-zA-Z0-9.+-]+;base64,/, "");
|
|
37
|
+
out[id] = await decodeImageBuffer(Buffer.from(raw, "base64"));
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function textResult(text) {
|
|
42
|
+
return { content: [{ type: "text", text }] };
|
|
43
|
+
}
|
|
44
|
+
function errorResult(message) {
|
|
45
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
46
|
+
}
|
|
47
|
+
function registerTools(server) {
|
|
48
|
+
const listOutputSizes = async () => textResult(JSON.stringify(STANDARD_OUTPUT_SIZES, null, 2));
|
|
49
|
+
server.registerTool(
|
|
50
|
+
"list_output_sizes",
|
|
51
|
+
{
|
|
52
|
+
title: "List output sizes",
|
|
53
|
+
description: "List standard App Store / Play Store output sizes (name + pixel dimensions) for use in a config's outputSizes array.",
|
|
54
|
+
inputSchema: {},
|
|
55
|
+
annotations: { readOnlyHint: true }
|
|
56
|
+
},
|
|
57
|
+
listOutputSizes
|
|
58
|
+
);
|
|
59
|
+
server.registerTool(
|
|
60
|
+
"list_devices",
|
|
61
|
+
{
|
|
62
|
+
title: "List output sizes (deprecated alias)",
|
|
63
|
+
description: "Deprecated alias of list_output_sizes. Prefer list_output_sizes.",
|
|
64
|
+
inputSchema: {},
|
|
65
|
+
annotations: { readOnlyHint: true }
|
|
66
|
+
},
|
|
67
|
+
listOutputSizes
|
|
68
|
+
);
|
|
69
|
+
server.registerTool(
|
|
70
|
+
"get_config_schema",
|
|
71
|
+
{
|
|
72
|
+
title: "Get the config JSON Schema",
|
|
73
|
+
description: "Return the JSON Schema for a mockup config, so you can author a valid config without reading source.",
|
|
74
|
+
inputSchema: {},
|
|
75
|
+
annotations: { readOnlyHint: true }
|
|
76
|
+
},
|
|
77
|
+
async () => textResult(
|
|
78
|
+
JSON.stringify(zodToJsonSchema(mockupConfigSchema, "MockupConfig"), null, 2)
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
server.registerTool(
|
|
82
|
+
"init_config",
|
|
83
|
+
{
|
|
84
|
+
title: "Create a starter config",
|
|
85
|
+
description: "Return a starter mockup config JSON for an app. Edit it, then call validate_config / preview_mockup / generate_mockups.",
|
|
86
|
+
inputSchema: { appName: z.string().describe("App display name") },
|
|
87
|
+
annotations: { readOnlyHint: true }
|
|
88
|
+
},
|
|
89
|
+
async ({ appName }) => textResult(JSON.stringify(createStarterConfig(appName), null, 2))
|
|
90
|
+
);
|
|
91
|
+
server.registerTool(
|
|
92
|
+
"list_templates",
|
|
93
|
+
{
|
|
94
|
+
title: "List demo templates",
|
|
95
|
+
description: "List ready-made design templates (id, name, description). Use apply_template to get one's full config, which includes a styled background, a positioned/rotated device frame, and example captions.",
|
|
96
|
+
inputSchema: {},
|
|
97
|
+
annotations: { readOnlyHint: true }
|
|
98
|
+
},
|
|
99
|
+
async () => textResult(
|
|
100
|
+
JSON.stringify(
|
|
101
|
+
TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description })),
|
|
102
|
+
null,
|
|
103
|
+
2
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
server.registerTool(
|
|
108
|
+
"apply_template",
|
|
109
|
+
{
|
|
110
|
+
title: "Apply a demo template",
|
|
111
|
+
description: "Return the full mockup config for a template id (from list_templates). Edit it (swap in your own screenshots/captions), then call preview_mockup or generate_mockups.",
|
|
112
|
+
inputSchema: { templateId: z.string().describe("Template id, e.g. 'aurora'") },
|
|
113
|
+
annotations: { readOnlyHint: true }
|
|
114
|
+
},
|
|
115
|
+
async ({ templateId }) => {
|
|
116
|
+
const template = getTemplate(templateId);
|
|
117
|
+
if (!template) {
|
|
118
|
+
return errorResult(
|
|
119
|
+
`Unknown template '${templateId}'. Available: ${TEMPLATES.map((t) => t.id).join(", ")}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return textResult(JSON.stringify(template.config, null, 2));
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
server.registerTool(
|
|
126
|
+
"list_themes",
|
|
127
|
+
{
|
|
128
|
+
title: "List accent color themes",
|
|
129
|
+
description: "List predefined color themes (id, name, palette). Use apply_theme to recolor an existing config with one \u2014 colors only, layout untouched.",
|
|
130
|
+
inputSchema: {},
|
|
131
|
+
annotations: { readOnlyHint: true }
|
|
132
|
+
},
|
|
133
|
+
async () => textResult(
|
|
134
|
+
JSON.stringify(
|
|
135
|
+
THEMES.map((t) => ({
|
|
136
|
+
id: t.id,
|
|
137
|
+
name: t.name,
|
|
138
|
+
backgroundColors: t.backgroundColors,
|
|
139
|
+
headlineColor: t.headlineColor,
|
|
140
|
+
accent: t.accent
|
|
141
|
+
})),
|
|
142
|
+
null,
|
|
143
|
+
2
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
server.registerTool(
|
|
148
|
+
"apply_theme",
|
|
149
|
+
{
|
|
150
|
+
title: "Apply an accent color theme",
|
|
151
|
+
description: "Recolor a mockup config with a theme (from list_themes): background colors, decoration accents, headline/subtitle/bezel colors. Layout, sizes and shadows are untouched. Returns the themed config JSON.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
config: mockupConfigSchema.describe("The mockup config to recolor"),
|
|
154
|
+
themeId: z.string().describe("Theme id, e.g. 'indigo'")
|
|
155
|
+
},
|
|
156
|
+
annotations: { readOnlyHint: true }
|
|
157
|
+
},
|
|
158
|
+
async ({ config, themeId }) => {
|
|
159
|
+
const theme = getTheme(themeId);
|
|
160
|
+
if (!theme) {
|
|
161
|
+
return errorResult(
|
|
162
|
+
`Unknown theme '${themeId}'. Available: ${THEMES.map((t) => t.id).join(", ")}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const cfg = parseConfigOrThrow(config);
|
|
167
|
+
return textResult(JSON.stringify(applyTheme(cfg, theme), null, 2));
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return errorResult(e.message);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
server.registerTool(
|
|
174
|
+
"validate_config",
|
|
175
|
+
{
|
|
176
|
+
title: "Validate a config",
|
|
177
|
+
description: "Validate a mockup config: schema correctness, a headline for every language on every screenshot, and (if baseDir given) that every screenshot file exists on disk.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
config: z.unknown().describe("The mockup config object to validate"),
|
|
180
|
+
baseDir: z.string().optional().describe("If set, also verify each screenshot file exists relative to this directory.")
|
|
181
|
+
},
|
|
182
|
+
annotations: { readOnlyHint: true }
|
|
183
|
+
},
|
|
184
|
+
async ({ config, baseDir }) => {
|
|
185
|
+
const parsed = mockupConfigSchema.safeParse(config);
|
|
186
|
+
if (!parsed.success) {
|
|
187
|
+
const problems2 = parsed.error.issues.map(
|
|
188
|
+
(i) => `${i.path.join(".") || "(root)"}: ${i.message}`
|
|
189
|
+
);
|
|
190
|
+
return textResult(`INVALID (schema):
|
|
191
|
+
- ${problems2.join("\n - ")}`);
|
|
192
|
+
}
|
|
193
|
+
const problems = validateConfig({ config: parsed.data, configDir: baseDir ?? "" });
|
|
194
|
+
const fileErrors = problems.filter((p) => p.startsWith("Screenshot not found:"));
|
|
195
|
+
const other = problems.filter((p) => !p.startsWith("Screenshot not found:"));
|
|
196
|
+
if (baseDir) {
|
|
197
|
+
return textResult(
|
|
198
|
+
problems.length === 0 ? "VALID" : `INVALID:
|
|
199
|
+
- ${problems.join("\n - ")}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const note = fileErrors.length > 0 ? `
|
|
203
|
+
NOTE: screenshot files were not checked (no baseDir); ${fileErrors.length} file path(s) unverified.` : "";
|
|
204
|
+
return textResult(
|
|
205
|
+
(other.length === 0 ? "VALID" : `INVALID:
|
|
206
|
+
- ${other.join("\n - ")}`) + note
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
server.registerTool(
|
|
211
|
+
"preview_mockup",
|
|
212
|
+
{
|
|
213
|
+
title: "Preview one mockup",
|
|
214
|
+
description: "Render a single mockup (one screenshot, language and size) and return it as a PNG image.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
config: mockupConfigSchema.describe("The mockup config"),
|
|
217
|
+
screenshotId: z.string().describe("Screenshot id from the config's screenshots[]"),
|
|
218
|
+
language: z.string().describe("Language code, e.g. 'en'"),
|
|
219
|
+
size: z.string().optional().describe("Output size name; defaults to the first in the config"),
|
|
220
|
+
baseDir: z.string().optional().describe("Directory to resolve screenshot file paths against (local use)"),
|
|
221
|
+
images: z.record(z.string(), z.string()).optional().describe("base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)")
|
|
222
|
+
},
|
|
223
|
+
annotations: { readOnlyHint: true }
|
|
224
|
+
},
|
|
225
|
+
async ({ config, screenshotId, language, size, baseDir, images }) => {
|
|
226
|
+
try {
|
|
227
|
+
const cfg = parseConfigOrThrow(config);
|
|
228
|
+
const decoded = await decodeImages(images);
|
|
229
|
+
const { buffer } = await renderOneToPng({
|
|
230
|
+
config: cfg,
|
|
231
|
+
configDir: baseDir ?? "",
|
|
232
|
+
screenshotId,
|
|
233
|
+
language,
|
|
234
|
+
sizeName: size,
|
|
235
|
+
images: decoded
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{ type: "image", data: buffer.toString("base64"), mimeType: "image/png" }
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return errorResult(e.message);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
server.registerTool(
|
|
248
|
+
"generate_mockups",
|
|
249
|
+
{
|
|
250
|
+
title: "Generate all mockups",
|
|
251
|
+
description: "Batch-render every screenshot \xD7 language \xD7 size to PNG files under outputDir (on the server filesystem), in <outputDir>/<size>/<lang>/<prefix>_<lang>_<id>.png layout. Returns the written paths.",
|
|
252
|
+
inputSchema: {
|
|
253
|
+
config: mockupConfigSchema.describe("The mockup config"),
|
|
254
|
+
outputDir: z.string().describe("Directory to write mockups into"),
|
|
255
|
+
baseDir: z.string().optional().describe("Directory to resolve screenshot file paths against (local use)"),
|
|
256
|
+
languages: z.array(z.string()).optional().describe("Language filter"),
|
|
257
|
+
screenshots: z.array(z.string()).optional().describe("Screenshot id filter"),
|
|
258
|
+
sizes: z.array(z.string()).optional().describe("Output size name filter"),
|
|
259
|
+
images: z.record(z.string(), z.string()).optional().describe("base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)")
|
|
260
|
+
},
|
|
261
|
+
annotations: { readOnlyHint: false }
|
|
262
|
+
},
|
|
263
|
+
async ({ config, outputDir, baseDir, languages, screenshots, sizes, images }) => {
|
|
264
|
+
try {
|
|
265
|
+
const cfg = parseConfigOrThrow(config);
|
|
266
|
+
const decoded = await decodeImages(images);
|
|
267
|
+
const paths = await generate({
|
|
268
|
+
config: cfg,
|
|
269
|
+
configDir: baseDir ?? "",
|
|
270
|
+
outputDir,
|
|
271
|
+
languages,
|
|
272
|
+
screenshots,
|
|
273
|
+
sizes,
|
|
274
|
+
images: decoded
|
|
275
|
+
});
|
|
276
|
+
return textResult(`Generated ${paths.length} mockups:
|
|
277
|
+
${paths.join("\n")}`);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return errorResult(e.message);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
function createServer() {
|
|
285
|
+
const server = new McpServer({ name: "appmockup", version: SERVER_VERSION });
|
|
286
|
+
registerTools(server);
|
|
287
|
+
return server;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export {
|
|
291
|
+
createServer
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=chunk-MGDARVK4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport {\n mockupConfigSchema,\n createStarterConfig,\n STANDARD_OUTPUT_SIZES,\n TEMPLATES,\n getTemplate,\n THEMES,\n getTheme,\n applyTheme,\n type MockupConfig,\n type ScreenshotImage,\n} from \"@appmockup/core\";\nimport {\n renderOneToPng,\n generate,\n validateConfig,\n decodeImageBuffer,\n} from \"@appmockup/node-render\";\n\nconst SERVER_VERSION = \"0.3.0\";\n\n/** Parse a possibly-invalid config, throwing a readable aggregate error. */\nfunction parseConfigOrThrow(input: unknown): MockupConfig {\n const result = mockupConfigSchema.safeParse(input);\n if (!result.success) {\n const detail = result.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Invalid config: ${detail}`);\n }\n return result.data;\n}\n\n/** Decode an optional map of base64 PNGs (data-URI or raw) into drawable images. */\nasync function decodeImages(\n images: Record<string, string> | undefined,\n): Promise<Record<string, ScreenshotImage> | undefined> {\n if (!images) return undefined;\n const out: Record<string, ScreenshotImage> = {};\n for (const [id, value] of Object.entries(images)) {\n const raw = value.replace(/^data:image\\/[a-zA-Z0-9.+-]+;base64,/, \"\");\n out[id] = await decodeImageBuffer(Buffer.from(raw, \"base64\"));\n }\n return out;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: \"text\" as const, text }] };\n}\n\nfunction errorResult(message: string) {\n return { isError: true, content: [{ type: \"text\" as const, text: message }] };\n}\n\n/**\n * Build the AppMockup MCP server. Used by both the stdio binary (local) and the\n * hosted Streamable-HTTP route (web). Screenshots can be supplied either via a\n * `baseDir` (resolve file paths from disk — local) or an `images` map of base64\n * PNGs keyed by screenshot id (hosted).\n */\n/** Register all AppMockup tools on a server instance (shared by stdio + hosted HTTP). */\nexport function registerTools(server: McpServer): void {\n const listOutputSizes = async () =>\n textResult(JSON.stringify(STANDARD_OUTPUT_SIZES, null, 2));\n\n server.registerTool(\n \"list_output_sizes\",\n {\n title: \"List output sizes\",\n description:\n \"List standard App Store / Play Store output sizes (name + pixel dimensions) for use in a config's outputSizes array.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n listOutputSizes,\n );\n\n // Deprecated alias for backward compatibility with earlier clients.\n server.registerTool(\n \"list_devices\",\n {\n title: \"List output sizes (deprecated alias)\",\n description: \"Deprecated alias of list_output_sizes. Prefer list_output_sizes.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n listOutputSizes,\n );\n\n server.registerTool(\n \"get_config_schema\",\n {\n title: \"Get the config JSON Schema\",\n description:\n \"Return the JSON Schema for a mockup config, so you can author a valid config without reading source.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(zodToJsonSchema(mockupConfigSchema, \"MockupConfig\"), null, 2),\n ),\n );\n\n server.registerTool(\n \"init_config\",\n {\n title: \"Create a starter config\",\n description:\n \"Return a starter mockup config JSON for an app. Edit it, then call validate_config / preview_mockup / generate_mockups.\",\n inputSchema: { appName: z.string().describe(\"App display name\") },\n annotations: { readOnlyHint: true },\n },\n async ({ appName }) =>\n textResult(JSON.stringify(createStarterConfig(appName), null, 2)),\n );\n\n server.registerTool(\n \"list_templates\",\n {\n title: \"List demo templates\",\n description:\n \"List ready-made design templates (id, name, description). Use apply_template to get one's full config, which includes a styled background, a positioned/rotated device frame, and example captions.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(\n TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description })),\n null,\n 2,\n ),\n ),\n );\n\n server.registerTool(\n \"apply_template\",\n {\n title: \"Apply a demo template\",\n description:\n \"Return the full mockup config for a template id (from list_templates). Edit it (swap in your own screenshots/captions), then call preview_mockup or generate_mockups.\",\n inputSchema: { templateId: z.string().describe(\"Template id, e.g. 'aurora'\") },\n annotations: { readOnlyHint: true },\n },\n async ({ templateId }) => {\n const template = getTemplate(templateId);\n if (!template) {\n return errorResult(\n `Unknown template '${templateId}'. Available: ${TEMPLATES.map((t) => t.id).join(\", \")}`,\n );\n }\n return textResult(JSON.stringify(template.config, null, 2));\n },\n );\n\n server.registerTool(\n \"list_themes\",\n {\n title: \"List accent color themes\",\n description:\n \"List predefined color themes (id, name, palette). Use apply_theme to recolor an existing config with one — colors only, layout untouched.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(\n THEMES.map((t) => ({\n id: t.id,\n name: t.name,\n backgroundColors: t.backgroundColors,\n headlineColor: t.headlineColor,\n accent: t.accent,\n })),\n null,\n 2,\n ),\n ),\n );\n\n server.registerTool(\n \"apply_theme\",\n {\n title: \"Apply an accent color theme\",\n description:\n \"Recolor a mockup config with a theme (from list_themes): background colors, decoration accents, headline/subtitle/bezel colors. Layout, sizes and shadows are untouched. Returns the themed config JSON.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config to recolor\"),\n themeId: z.string().describe(\"Theme id, e.g. 'indigo'\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, themeId }) => {\n const theme = getTheme(themeId);\n if (!theme) {\n return errorResult(\n `Unknown theme '${themeId}'. Available: ${THEMES.map((t) => t.id).join(\", \")}`,\n );\n }\n try {\n const cfg = parseConfigOrThrow(config);\n return textResult(JSON.stringify(applyTheme(cfg, theme), null, 2));\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n \"validate_config\",\n {\n title: \"Validate a config\",\n description:\n \"Validate a mockup config: schema correctness, a headline for every language on every screenshot, and (if baseDir given) that every screenshot file exists on disk.\",\n inputSchema: {\n config: z.unknown().describe(\"The mockup config object to validate\"),\n baseDir: z\n .string()\n .optional()\n .describe(\"If set, also verify each screenshot file exists relative to this directory.\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, baseDir }) => {\n const parsed = mockupConfigSchema.safeParse(config);\n if (!parsed.success) {\n const problems = parsed.error.issues.map(\n (i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`,\n );\n return textResult(`INVALID (schema):\\n - ${problems.join(\"\\n - \")}`);\n }\n const problems = validateConfig({ config: parsed.data, configDir: baseDir ?? \"\" });\n // When no baseDir was provided, screenshot files can't be checked — surface that\n // as a NOTE rather than silently hiding those errors.\n const fileErrors = problems.filter((p) => p.startsWith(\"Screenshot not found:\"));\n const other = problems.filter((p) => !p.startsWith(\"Screenshot not found:\"));\n if (baseDir) {\n return textResult(\n problems.length === 0 ? \"VALID\" : `INVALID:\\n - ${problems.join(\"\\n - \")}`,\n );\n }\n const note =\n fileErrors.length > 0\n ? `\\nNOTE: screenshot files were not checked (no baseDir); ${fileErrors.length} file path(s) unverified.`\n : \"\";\n return textResult(\n (other.length === 0 ? \"VALID\" : `INVALID:\\n - ${other.join(\"\\n - \")}`) + note,\n );\n },\n );\n\n server.registerTool(\n \"preview_mockup\",\n {\n title: \"Preview one mockup\",\n description:\n \"Render a single mockup (one screenshot, language and size) and return it as a PNG image.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config\"),\n screenshotId: z.string().describe(\"Screenshot id from the config's screenshots[]\"),\n language: z.string().describe(\"Language code, e.g. 'en'\"),\n size: z.string().optional().describe(\"Output size name; defaults to the first in the config\"),\n baseDir: z.string().optional().describe(\"Directory to resolve screenshot file paths against (local use)\"),\n images: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, screenshotId, language, size, baseDir, images }) => {\n try {\n const cfg = parseConfigOrThrow(config);\n const decoded = await decodeImages(images);\n const { buffer } = await renderOneToPng({\n config: cfg,\n configDir: baseDir ?? \"\",\n screenshotId,\n language,\n sizeName: size,\n images: decoded,\n });\n return {\n content: [\n { type: \"image\" as const, data: buffer.toString(\"base64\"), mimeType: \"image/png\" },\n ],\n };\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n \"generate_mockups\",\n {\n title: \"Generate all mockups\",\n description:\n \"Batch-render every screenshot × language × size to PNG files under outputDir (on the server filesystem), in <outputDir>/<size>/<lang>/<prefix>_<lang>_<id>.png layout. Returns the written paths.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config\"),\n outputDir: z.string().describe(\"Directory to write mockups into\"),\n baseDir: z.string().optional().describe(\"Directory to resolve screenshot file paths against (local use)\"),\n languages: z.array(z.string()).optional().describe(\"Language filter\"),\n screenshots: z.array(z.string()).optional().describe(\"Screenshot id filter\"),\n sizes: z.array(z.string()).optional().describe(\"Output size name filter\"),\n images: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)\"),\n },\n annotations: { readOnlyHint: false },\n },\n async ({ config, outputDir, baseDir, languages, screenshots, sizes, images }) => {\n try {\n const cfg = parseConfigOrThrow(config);\n const decoded = await decodeImages(images);\n const paths = await generate({\n config: cfg,\n configDir: baseDir ?? \"\",\n outputDir,\n languages,\n screenshots,\n sizes,\n images: decoded,\n });\n return textResult(`Generated ${paths.length} mockups:\\n${paths.join(\"\\n\")}`);\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n}\n\n/** Build a fully-configured AppMockup MCP server (used by the stdio binary). */\nexport function createServer(): McpServer {\n const server = new McpServer({ name: \"appmockup\", version: SERVER_VERSION });\n registerTools(server);\n return server;\n}\n"],"mappings":"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","names":["problems"]}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-MGDARVK4.js";
|
|
5
|
+
|
|
6
|
+
// src/http.ts
|
|
7
|
+
import {
|
|
8
|
+
createServer as createHttpServer
|
|
9
|
+
} from "http";
|
|
10
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
|
+
var PORT = Number(process.env.PORT ?? 3333);
|
|
12
|
+
var MCP_PATH = process.env.MCP_PATH ?? "/mcp";
|
|
13
|
+
function readBody(req) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
req.on("data", (c) => chunks.push(c));
|
|
17
|
+
req.on("end", () => {
|
|
18
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
19
|
+
if (!raw) return resolve(void 0);
|
|
20
|
+
try {
|
|
21
|
+
resolve(JSON.parse(raw));
|
|
22
|
+
} catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
req.on("error", reject);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
var httpServer = createHttpServer(async (req, res) => {
|
|
30
|
+
const path = (req.url ?? "").split("?")[0];
|
|
31
|
+
if (path === "/health") {
|
|
32
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ ok: true }));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (path !== MCP_PATH) {
|
|
36
|
+
res.writeHead(404, { "Content-Type": "application/json" }).end(JSON.stringify({ error: "Not found" }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const body = req.method === "POST" ? await readBody(req) : void 0;
|
|
41
|
+
const server = createServer();
|
|
42
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
43
|
+
res.on("close", () => {
|
|
44
|
+
void transport.close();
|
|
45
|
+
void server.close();
|
|
46
|
+
});
|
|
47
|
+
await server.connect(transport);
|
|
48
|
+
await transport.handleRequest(req, res, body);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (!res.headersSent) {
|
|
51
|
+
res.writeHead(500, { "Content-Type": "application/json" }).end(
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
jsonrpc: "2.0",
|
|
54
|
+
error: { code: -32603, message: e.message },
|
|
55
|
+
id: null
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
httpServer.listen(PORT, () => {
|
|
62
|
+
console.error(`appmockup MCP server (Streamable HTTP) on http://localhost:${PORT}${MCP_PATH}`);
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http.ts"],"sourcesContent":["import {\n createServer as createHttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { createServer } from \"./server.js\";\n\n/**\n * Standalone hosted MCP server over Streamable HTTP (stateless). Deploy anywhere\n * that runs Node (Fly.io, Render, Railway, a VM) — it links @napi-rs/canvas\n * natively, with no bundler in the way. Point any MCP client at\n * http://<host>:<port>/mcp.\n *\n * Env: PORT (default 3333), MCP_PATH (default /mcp).\n */\nconst PORT = Number(process.env.PORT ?? 3333);\nconst MCP_PATH = process.env.MCP_PATH ?? \"/mcp\";\n\nfunction readBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n if (!raw) return resolve(undefined);\n try {\n resolve(JSON.parse(raw));\n } catch (e) {\n reject(e);\n }\n });\n req.on(\"error\", reject);\n });\n}\n\nconst httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {\n const path = (req.url ?? \"\").split(\"?\")[0];\n\n if (path === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" }).end(JSON.stringify({ ok: true }));\n return;\n }\n if (path !== MCP_PATH) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" }).end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n\n try {\n const body = req.method === \"POST\" ? await readBody(req) : undefined;\n // Stateless: a fresh server + transport per request (sessionIdGenerator: undefined).\n const server = createServer();\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });\n res.on(\"close\", () => {\n void transport.close();\n void server.close();\n });\n await server.connect(transport);\n await transport.handleRequest(req, res, body);\n } catch (e) {\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" }).end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: { code: -32603, message: (e as Error).message },\n id: null,\n }),\n );\n }\n }\n});\n\nhttpServer.listen(PORT, () => {\n console.error(`appmockup MCP server (Streamable HTTP) on http://localhost:${PORT}${MCP_PATH}`);\n});\n"],"mappings":";;;;;;AAAA;AAAA,EACE,gBAAgB;AAAA,OAGX;AACP,SAAS,qCAAqC;AAW9C,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAC5C,IAAM,WAAW,QAAQ,IAAI,YAAY;AAEzC,SAAS,SAAS,KAAwC;AACxD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,QAAI,GAAG,OAAO,MAAM;AAClB,YAAM,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AACjD,UAAI,CAAC,IAAK,QAAO,QAAQ,MAAS;AAClC,UAAI;AACF,gBAAQ,KAAK,MAAM,GAAG,CAAC;AAAA,MACzB,SAAS,GAAG;AACV,eAAO,CAAC;AAAA,MACV;AAAA,IACF,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEA,IAAM,aAAa,iBAAiB,OAAO,KAAsB,QAAwB;AACvF,QAAM,QAAQ,IAAI,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AAEzC,MAAI,SAAS,WAAW;AACtB,QAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC,EAAE,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AAC3F;AAAA,EACF;AACA,MAAI,SAAS,UAAU;AACrB,QAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC,EAAE,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AACrG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,OAAO,IAAI,WAAW,SAAS,MAAM,SAAS,GAAG,IAAI;AAE3D,UAAM,SAAS,aAAa;AAC5B,UAAM,YAAY,IAAI,8BAA8B,EAAE,oBAAoB,OAAU,CAAC;AACrF,QAAI,GAAG,SAAS,MAAM;AACpB,WAAK,UAAU,MAAM;AACrB,WAAK,OAAO,MAAM;AAAA,IACpB,CAAC;AACD,UAAM,OAAO,QAAQ,SAAS;AAC9B,UAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAAA,EAC9C,SAAS,GAAG;AACV,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC,EAAE;AAAA,QACzD,KAAK,UAAU;AAAA,UACb,SAAS;AAAA,UACT,OAAO,EAAE,MAAM,QAAQ,SAAU,EAAY,QAAQ;AAAA,UACrD,IAAI;AAAA,QACN,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,WAAW,OAAO,MAAM,MAAM;AAC5B,UAAQ,MAAM,8DAA8D,IAAI,GAAG,QAAQ,EAAE;AAC/F,CAAC;","names":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build the AppMockup MCP server. Used by both the stdio binary (local) and the
|
|
5
|
+
* hosted Streamable-HTTP route (web). Screenshots can be supplied either via a
|
|
6
|
+
* `baseDir` (resolve file paths from disk — local) or an `images` map of base64
|
|
7
|
+
* PNGs keyed by screenshot id (hosted).
|
|
8
|
+
*/
|
|
9
|
+
/** Register all AppMockup tools on a server instance (shared by stdio + hosted HTTP). */
|
|
10
|
+
declare function registerTools(server: McpServer): void;
|
|
11
|
+
/** Build a fully-configured AppMockup MCP server (used by the stdio binary). */
|
|
12
|
+
declare function createServer(): McpServer;
|
|
13
|
+
|
|
14
|
+
export { createServer, registerTools };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
|
+
import {
|
|
6
|
+
mockupConfigSchema,
|
|
7
|
+
createStarterConfig,
|
|
8
|
+
STANDARD_OUTPUT_SIZES,
|
|
9
|
+
TEMPLATES,
|
|
10
|
+
getTemplate,
|
|
11
|
+
THEMES,
|
|
12
|
+
getTheme,
|
|
13
|
+
applyTheme
|
|
14
|
+
} from "@appmockup/core";
|
|
15
|
+
import {
|
|
16
|
+
renderOneToPng,
|
|
17
|
+
generate,
|
|
18
|
+
validateConfig,
|
|
19
|
+
decodeImageBuffer
|
|
20
|
+
} from "@appmockup/node-render";
|
|
21
|
+
var SERVER_VERSION = "0.3.0";
|
|
22
|
+
function parseConfigOrThrow(input) {
|
|
23
|
+
const result = mockupConfigSchema.safeParse(input);
|
|
24
|
+
if (!result.success) {
|
|
25
|
+
const detail = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
26
|
+
throw new Error(`Invalid config: ${detail}`);
|
|
27
|
+
}
|
|
28
|
+
return result.data;
|
|
29
|
+
}
|
|
30
|
+
async function decodeImages(images) {
|
|
31
|
+
if (!images) return void 0;
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [id, value] of Object.entries(images)) {
|
|
34
|
+
const raw = value.replace(/^data:image\/[a-zA-Z0-9.+-]+;base64,/, "");
|
|
35
|
+
out[id] = await decodeImageBuffer(Buffer.from(raw, "base64"));
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function textResult(text) {
|
|
40
|
+
return { content: [{ type: "text", text }] };
|
|
41
|
+
}
|
|
42
|
+
function errorResult(message) {
|
|
43
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
44
|
+
}
|
|
45
|
+
function registerTools(server) {
|
|
46
|
+
const listOutputSizes = async () => textResult(JSON.stringify(STANDARD_OUTPUT_SIZES, null, 2));
|
|
47
|
+
server.registerTool(
|
|
48
|
+
"list_output_sizes",
|
|
49
|
+
{
|
|
50
|
+
title: "List output sizes",
|
|
51
|
+
description: "List standard App Store / Play Store output sizes (name + pixel dimensions) for use in a config's outputSizes array.",
|
|
52
|
+
inputSchema: {},
|
|
53
|
+
annotations: { readOnlyHint: true }
|
|
54
|
+
},
|
|
55
|
+
listOutputSizes
|
|
56
|
+
);
|
|
57
|
+
server.registerTool(
|
|
58
|
+
"list_devices",
|
|
59
|
+
{
|
|
60
|
+
title: "List output sizes (deprecated alias)",
|
|
61
|
+
description: "Deprecated alias of list_output_sizes. Prefer list_output_sizes.",
|
|
62
|
+
inputSchema: {},
|
|
63
|
+
annotations: { readOnlyHint: true }
|
|
64
|
+
},
|
|
65
|
+
listOutputSizes
|
|
66
|
+
);
|
|
67
|
+
server.registerTool(
|
|
68
|
+
"get_config_schema",
|
|
69
|
+
{
|
|
70
|
+
title: "Get the config JSON Schema",
|
|
71
|
+
description: "Return the JSON Schema for a mockup config, so you can author a valid config without reading source.",
|
|
72
|
+
inputSchema: {},
|
|
73
|
+
annotations: { readOnlyHint: true }
|
|
74
|
+
},
|
|
75
|
+
async () => textResult(
|
|
76
|
+
JSON.stringify(zodToJsonSchema(mockupConfigSchema, "MockupConfig"), null, 2)
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
server.registerTool(
|
|
80
|
+
"init_config",
|
|
81
|
+
{
|
|
82
|
+
title: "Create a starter config",
|
|
83
|
+
description: "Return a starter mockup config JSON for an app. Edit it, then call validate_config / preview_mockup / generate_mockups.",
|
|
84
|
+
inputSchema: { appName: z.string().describe("App display name") },
|
|
85
|
+
annotations: { readOnlyHint: true }
|
|
86
|
+
},
|
|
87
|
+
async ({ appName }) => textResult(JSON.stringify(createStarterConfig(appName), null, 2))
|
|
88
|
+
);
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"list_templates",
|
|
91
|
+
{
|
|
92
|
+
title: "List demo templates",
|
|
93
|
+
description: "List ready-made design templates (id, name, description). Use apply_template to get one's full config, which includes a styled background, a positioned/rotated device frame, and example captions.",
|
|
94
|
+
inputSchema: {},
|
|
95
|
+
annotations: { readOnlyHint: true }
|
|
96
|
+
},
|
|
97
|
+
async () => textResult(
|
|
98
|
+
JSON.stringify(
|
|
99
|
+
TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description })),
|
|
100
|
+
null,
|
|
101
|
+
2
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
server.registerTool(
|
|
106
|
+
"apply_template",
|
|
107
|
+
{
|
|
108
|
+
title: "Apply a demo template",
|
|
109
|
+
description: "Return the full mockup config for a template id (from list_templates). Edit it (swap in your own screenshots/captions), then call preview_mockup or generate_mockups.",
|
|
110
|
+
inputSchema: { templateId: z.string().describe("Template id, e.g. 'aurora'") },
|
|
111
|
+
annotations: { readOnlyHint: true }
|
|
112
|
+
},
|
|
113
|
+
async ({ templateId }) => {
|
|
114
|
+
const template = getTemplate(templateId);
|
|
115
|
+
if (!template) {
|
|
116
|
+
return errorResult(
|
|
117
|
+
`Unknown template '${templateId}'. Available: ${TEMPLATES.map((t) => t.id).join(", ")}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return textResult(JSON.stringify(template.config, null, 2));
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
server.registerTool(
|
|
124
|
+
"list_themes",
|
|
125
|
+
{
|
|
126
|
+
title: "List accent color themes",
|
|
127
|
+
description: "List predefined color themes (id, name, palette). Use apply_theme to recolor an existing config with one \u2014 colors only, layout untouched.",
|
|
128
|
+
inputSchema: {},
|
|
129
|
+
annotations: { readOnlyHint: true }
|
|
130
|
+
},
|
|
131
|
+
async () => textResult(
|
|
132
|
+
JSON.stringify(
|
|
133
|
+
THEMES.map((t) => ({
|
|
134
|
+
id: t.id,
|
|
135
|
+
name: t.name,
|
|
136
|
+
backgroundColors: t.backgroundColors,
|
|
137
|
+
headlineColor: t.headlineColor,
|
|
138
|
+
accent: t.accent
|
|
139
|
+
})),
|
|
140
|
+
null,
|
|
141
|
+
2
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
server.registerTool(
|
|
146
|
+
"apply_theme",
|
|
147
|
+
{
|
|
148
|
+
title: "Apply an accent color theme",
|
|
149
|
+
description: "Recolor a mockup config with a theme (from list_themes): background colors, decoration accents, headline/subtitle/bezel colors. Layout, sizes and shadows are untouched. Returns the themed config JSON.",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
config: mockupConfigSchema.describe("The mockup config to recolor"),
|
|
152
|
+
themeId: z.string().describe("Theme id, e.g. 'indigo'")
|
|
153
|
+
},
|
|
154
|
+
annotations: { readOnlyHint: true }
|
|
155
|
+
},
|
|
156
|
+
async ({ config, themeId }) => {
|
|
157
|
+
const theme = getTheme(themeId);
|
|
158
|
+
if (!theme) {
|
|
159
|
+
return errorResult(
|
|
160
|
+
`Unknown theme '${themeId}'. Available: ${THEMES.map((t) => t.id).join(", ")}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const cfg = parseConfigOrThrow(config);
|
|
165
|
+
return textResult(JSON.stringify(applyTheme(cfg, theme), null, 2));
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return errorResult(e.message);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
server.registerTool(
|
|
172
|
+
"validate_config",
|
|
173
|
+
{
|
|
174
|
+
title: "Validate a config",
|
|
175
|
+
description: "Validate a mockup config: schema correctness, a headline for every language on every screenshot, and (if baseDir given) that every screenshot file exists on disk.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
config: z.unknown().describe("The mockup config object to validate"),
|
|
178
|
+
baseDir: z.string().optional().describe("If set, also verify each screenshot file exists relative to this directory.")
|
|
179
|
+
},
|
|
180
|
+
annotations: { readOnlyHint: true }
|
|
181
|
+
},
|
|
182
|
+
async ({ config, baseDir }) => {
|
|
183
|
+
const parsed = mockupConfigSchema.safeParse(config);
|
|
184
|
+
if (!parsed.success) {
|
|
185
|
+
const problems2 = parsed.error.issues.map(
|
|
186
|
+
(i) => `${i.path.join(".") || "(root)"}: ${i.message}`
|
|
187
|
+
);
|
|
188
|
+
return textResult(`INVALID (schema):
|
|
189
|
+
- ${problems2.join("\n - ")}`);
|
|
190
|
+
}
|
|
191
|
+
const problems = validateConfig({ config: parsed.data, configDir: baseDir ?? "" });
|
|
192
|
+
const fileErrors = problems.filter((p) => p.startsWith("Screenshot not found:"));
|
|
193
|
+
const other = problems.filter((p) => !p.startsWith("Screenshot not found:"));
|
|
194
|
+
if (baseDir) {
|
|
195
|
+
return textResult(
|
|
196
|
+
problems.length === 0 ? "VALID" : `INVALID:
|
|
197
|
+
- ${problems.join("\n - ")}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const note = fileErrors.length > 0 ? `
|
|
201
|
+
NOTE: screenshot files were not checked (no baseDir); ${fileErrors.length} file path(s) unverified.` : "";
|
|
202
|
+
return textResult(
|
|
203
|
+
(other.length === 0 ? "VALID" : `INVALID:
|
|
204
|
+
- ${other.join("\n - ")}`) + note
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
server.registerTool(
|
|
209
|
+
"preview_mockup",
|
|
210
|
+
{
|
|
211
|
+
title: "Preview one mockup",
|
|
212
|
+
description: "Render a single mockup (one screenshot, language and size) and return it as a PNG image.",
|
|
213
|
+
inputSchema: {
|
|
214
|
+
config: mockupConfigSchema.describe("The mockup config"),
|
|
215
|
+
screenshotId: z.string().describe("Screenshot id from the config's screenshots[]"),
|
|
216
|
+
language: z.string().describe("Language code, e.g. 'en'"),
|
|
217
|
+
size: z.string().optional().describe("Output size name; defaults to the first in the config"),
|
|
218
|
+
baseDir: z.string().optional().describe("Directory to resolve screenshot file paths against (local use)"),
|
|
219
|
+
images: z.record(z.string(), z.string()).optional().describe("base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)")
|
|
220
|
+
},
|
|
221
|
+
annotations: { readOnlyHint: true }
|
|
222
|
+
},
|
|
223
|
+
async ({ config, screenshotId, language, size, baseDir, images }) => {
|
|
224
|
+
try {
|
|
225
|
+
const cfg = parseConfigOrThrow(config);
|
|
226
|
+
const decoded = await decodeImages(images);
|
|
227
|
+
const { buffer } = await renderOneToPng({
|
|
228
|
+
config: cfg,
|
|
229
|
+
configDir: baseDir ?? "",
|
|
230
|
+
screenshotId,
|
|
231
|
+
language,
|
|
232
|
+
sizeName: size,
|
|
233
|
+
images: decoded
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{ type: "image", data: buffer.toString("base64"), mimeType: "image/png" }
|
|
238
|
+
]
|
|
239
|
+
};
|
|
240
|
+
} catch (e) {
|
|
241
|
+
return errorResult(e.message);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
server.registerTool(
|
|
246
|
+
"generate_mockups",
|
|
247
|
+
{
|
|
248
|
+
title: "Generate all mockups",
|
|
249
|
+
description: "Batch-render every screenshot \xD7 language \xD7 size to PNG files under outputDir (on the server filesystem), in <outputDir>/<size>/<lang>/<prefix>_<lang>_<id>.png layout. Returns the written paths.",
|
|
250
|
+
inputSchema: {
|
|
251
|
+
config: mockupConfigSchema.describe("The mockup config"),
|
|
252
|
+
outputDir: z.string().describe("Directory to write mockups into"),
|
|
253
|
+
baseDir: z.string().optional().describe("Directory to resolve screenshot file paths against (local use)"),
|
|
254
|
+
languages: z.array(z.string()).optional().describe("Language filter"),
|
|
255
|
+
screenshots: z.array(z.string()).optional().describe("Screenshot id filter"),
|
|
256
|
+
sizes: z.array(z.string()).optional().describe("Output size name filter"),
|
|
257
|
+
images: z.record(z.string(), z.string()).optional().describe("base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)")
|
|
258
|
+
},
|
|
259
|
+
annotations: { readOnlyHint: false }
|
|
260
|
+
},
|
|
261
|
+
async ({ config, outputDir, baseDir, languages, screenshots, sizes, images }) => {
|
|
262
|
+
try {
|
|
263
|
+
const cfg = parseConfigOrThrow(config);
|
|
264
|
+
const decoded = await decodeImages(images);
|
|
265
|
+
const paths = await generate({
|
|
266
|
+
config: cfg,
|
|
267
|
+
configDir: baseDir ?? "",
|
|
268
|
+
outputDir,
|
|
269
|
+
languages,
|
|
270
|
+
screenshots,
|
|
271
|
+
sizes,
|
|
272
|
+
images: decoded
|
|
273
|
+
});
|
|
274
|
+
return textResult(`Generated ${paths.length} mockups:
|
|
275
|
+
${paths.join("\n")}`);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
return errorResult(e.message);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
function createServer() {
|
|
283
|
+
const server = new McpServer({ name: "appmockup", version: SERVER_VERSION });
|
|
284
|
+
registerTools(server);
|
|
285
|
+
return server;
|
|
286
|
+
}
|
|
287
|
+
export {
|
|
288
|
+
createServer,
|
|
289
|
+
registerTools
|
|
290
|
+
};
|
|
291
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport {\n mockupConfigSchema,\n createStarterConfig,\n STANDARD_OUTPUT_SIZES,\n TEMPLATES,\n getTemplate,\n THEMES,\n getTheme,\n applyTheme,\n type MockupConfig,\n type ScreenshotImage,\n} from \"@appmockup/core\";\nimport {\n renderOneToPng,\n generate,\n validateConfig,\n decodeImageBuffer,\n} from \"@appmockup/node-render\";\n\nconst SERVER_VERSION = \"0.3.0\";\n\n/** Parse a possibly-invalid config, throwing a readable aggregate error. */\nfunction parseConfigOrThrow(input: unknown): MockupConfig {\n const result = mockupConfigSchema.safeParse(input);\n if (!result.success) {\n const detail = result.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Invalid config: ${detail}`);\n }\n return result.data;\n}\n\n/** Decode an optional map of base64 PNGs (data-URI or raw) into drawable images. */\nasync function decodeImages(\n images: Record<string, string> | undefined,\n): Promise<Record<string, ScreenshotImage> | undefined> {\n if (!images) return undefined;\n const out: Record<string, ScreenshotImage> = {};\n for (const [id, value] of Object.entries(images)) {\n const raw = value.replace(/^data:image\\/[a-zA-Z0-9.+-]+;base64,/, \"\");\n out[id] = await decodeImageBuffer(Buffer.from(raw, \"base64\"));\n }\n return out;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: \"text\" as const, text }] };\n}\n\nfunction errorResult(message: string) {\n return { isError: true, content: [{ type: \"text\" as const, text: message }] };\n}\n\n/**\n * Build the AppMockup MCP server. Used by both the stdio binary (local) and the\n * hosted Streamable-HTTP route (web). Screenshots can be supplied either via a\n * `baseDir` (resolve file paths from disk — local) or an `images` map of base64\n * PNGs keyed by screenshot id (hosted).\n */\n/** Register all AppMockup tools on a server instance (shared by stdio + hosted HTTP). */\nexport function registerTools(server: McpServer): void {\n const listOutputSizes = async () =>\n textResult(JSON.stringify(STANDARD_OUTPUT_SIZES, null, 2));\n\n server.registerTool(\n \"list_output_sizes\",\n {\n title: \"List output sizes\",\n description:\n \"List standard App Store / Play Store output sizes (name + pixel dimensions) for use in a config's outputSizes array.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n listOutputSizes,\n );\n\n // Deprecated alias for backward compatibility with earlier clients.\n server.registerTool(\n \"list_devices\",\n {\n title: \"List output sizes (deprecated alias)\",\n description: \"Deprecated alias of list_output_sizes. Prefer list_output_sizes.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n listOutputSizes,\n );\n\n server.registerTool(\n \"get_config_schema\",\n {\n title: \"Get the config JSON Schema\",\n description:\n \"Return the JSON Schema for a mockup config, so you can author a valid config without reading source.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(zodToJsonSchema(mockupConfigSchema, \"MockupConfig\"), null, 2),\n ),\n );\n\n server.registerTool(\n \"init_config\",\n {\n title: \"Create a starter config\",\n description:\n \"Return a starter mockup config JSON for an app. Edit it, then call validate_config / preview_mockup / generate_mockups.\",\n inputSchema: { appName: z.string().describe(\"App display name\") },\n annotations: { readOnlyHint: true },\n },\n async ({ appName }) =>\n textResult(JSON.stringify(createStarterConfig(appName), null, 2)),\n );\n\n server.registerTool(\n \"list_templates\",\n {\n title: \"List demo templates\",\n description:\n \"List ready-made design templates (id, name, description). Use apply_template to get one's full config, which includes a styled background, a positioned/rotated device frame, and example captions.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(\n TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description })),\n null,\n 2,\n ),\n ),\n );\n\n server.registerTool(\n \"apply_template\",\n {\n title: \"Apply a demo template\",\n description:\n \"Return the full mockup config for a template id (from list_templates). Edit it (swap in your own screenshots/captions), then call preview_mockup or generate_mockups.\",\n inputSchema: { templateId: z.string().describe(\"Template id, e.g. 'aurora'\") },\n annotations: { readOnlyHint: true },\n },\n async ({ templateId }) => {\n const template = getTemplate(templateId);\n if (!template) {\n return errorResult(\n `Unknown template '${templateId}'. Available: ${TEMPLATES.map((t) => t.id).join(\", \")}`,\n );\n }\n return textResult(JSON.stringify(template.config, null, 2));\n },\n );\n\n server.registerTool(\n \"list_themes\",\n {\n title: \"List accent color themes\",\n description:\n \"List predefined color themes (id, name, palette). Use apply_theme to recolor an existing config with one — colors only, layout untouched.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () =>\n textResult(\n JSON.stringify(\n THEMES.map((t) => ({\n id: t.id,\n name: t.name,\n backgroundColors: t.backgroundColors,\n headlineColor: t.headlineColor,\n accent: t.accent,\n })),\n null,\n 2,\n ),\n ),\n );\n\n server.registerTool(\n \"apply_theme\",\n {\n title: \"Apply an accent color theme\",\n description:\n \"Recolor a mockup config with a theme (from list_themes): background colors, decoration accents, headline/subtitle/bezel colors. Layout, sizes and shadows are untouched. Returns the themed config JSON.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config to recolor\"),\n themeId: z.string().describe(\"Theme id, e.g. 'indigo'\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, themeId }) => {\n const theme = getTheme(themeId);\n if (!theme) {\n return errorResult(\n `Unknown theme '${themeId}'. Available: ${THEMES.map((t) => t.id).join(\", \")}`,\n );\n }\n try {\n const cfg = parseConfigOrThrow(config);\n return textResult(JSON.stringify(applyTheme(cfg, theme), null, 2));\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n \"validate_config\",\n {\n title: \"Validate a config\",\n description:\n \"Validate a mockup config: schema correctness, a headline for every language on every screenshot, and (if baseDir given) that every screenshot file exists on disk.\",\n inputSchema: {\n config: z.unknown().describe(\"The mockup config object to validate\"),\n baseDir: z\n .string()\n .optional()\n .describe(\"If set, also verify each screenshot file exists relative to this directory.\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, baseDir }) => {\n const parsed = mockupConfigSchema.safeParse(config);\n if (!parsed.success) {\n const problems = parsed.error.issues.map(\n (i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`,\n );\n return textResult(`INVALID (schema):\\n - ${problems.join(\"\\n - \")}`);\n }\n const problems = validateConfig({ config: parsed.data, configDir: baseDir ?? \"\" });\n // When no baseDir was provided, screenshot files can't be checked — surface that\n // as a NOTE rather than silently hiding those errors.\n const fileErrors = problems.filter((p) => p.startsWith(\"Screenshot not found:\"));\n const other = problems.filter((p) => !p.startsWith(\"Screenshot not found:\"));\n if (baseDir) {\n return textResult(\n problems.length === 0 ? \"VALID\" : `INVALID:\\n - ${problems.join(\"\\n - \")}`,\n );\n }\n const note =\n fileErrors.length > 0\n ? `\\nNOTE: screenshot files were not checked (no baseDir); ${fileErrors.length} file path(s) unverified.`\n : \"\";\n return textResult(\n (other.length === 0 ? \"VALID\" : `INVALID:\\n - ${other.join(\"\\n - \")}`) + note,\n );\n },\n );\n\n server.registerTool(\n \"preview_mockup\",\n {\n title: \"Preview one mockup\",\n description:\n \"Render a single mockup (one screenshot, language and size) and return it as a PNG image.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config\"),\n screenshotId: z.string().describe(\"Screenshot id from the config's screenshots[]\"),\n language: z.string().describe(\"Language code, e.g. 'en'\"),\n size: z.string().optional().describe(\"Output size name; defaults to the first in the config\"),\n baseDir: z.string().optional().describe(\"Directory to resolve screenshot file paths against (local use)\"),\n images: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ config, screenshotId, language, size, baseDir, images }) => {\n try {\n const cfg = parseConfigOrThrow(config);\n const decoded = await decodeImages(images);\n const { buffer } = await renderOneToPng({\n config: cfg,\n configDir: baseDir ?? \"\",\n screenshotId,\n language,\n sizeName: size,\n images: decoded,\n });\n return {\n content: [\n { type: \"image\" as const, data: buffer.toString(\"base64\"), mimeType: \"image/png\" },\n ],\n };\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n\n server.registerTool(\n \"generate_mockups\",\n {\n title: \"Generate all mockups\",\n description:\n \"Batch-render every screenshot × language × size to PNG files under outputDir (on the server filesystem), in <outputDir>/<size>/<lang>/<prefix>_<lang>_<id>.png layout. Returns the written paths.\",\n inputSchema: {\n config: mockupConfigSchema.describe(\"The mockup config\"),\n outputDir: z.string().describe(\"Directory to write mockups into\"),\n baseDir: z.string().optional().describe(\"Directory to resolve screenshot file paths against (local use)\"),\n languages: z.array(z.string()).optional().describe(\"Language filter\"),\n screenshots: z.array(z.string()).optional().describe(\"Screenshot id filter\"),\n sizes: z.array(z.string()).optional().describe(\"Output size name filter\"),\n images: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"base64-encoded PNGs keyed by screenshot id; overrides file lookup (hosted use)\"),\n },\n annotations: { readOnlyHint: false },\n },\n async ({ config, outputDir, baseDir, languages, screenshots, sizes, images }) => {\n try {\n const cfg = parseConfigOrThrow(config);\n const decoded = await decodeImages(images);\n const paths = await generate({\n config: cfg,\n configDir: baseDir ?? \"\",\n outputDir,\n languages,\n screenshots,\n sizes,\n images: decoded,\n });\n return textResult(`Generated ${paths.length} mockups:\\n${paths.join(\"\\n\")}`);\n } catch (e) {\n return errorResult((e as Error).message);\n }\n },\n );\n}\n\n/** Build a fully-configured AppMockup MCP server (used by the stdio binary). */\nexport function createServer(): McpServer {\n const server = new McpServer({ name: \"appmockup\", version: SERVER_VERSION });\n registerTools(server);\n return server;\n}\n"],"mappings":"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","names":["problems"]}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-MGDARVK4.js";
|
|
5
|
+
|
|
6
|
+
// src/stdio.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
var server = createServer();
|
|
9
|
+
var transport = new StdioServerTransport();
|
|
10
|
+
await server.connect(transport);
|
|
11
|
+
console.error("appmockup MCP server running on stdio");
|
|
12
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stdio.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nconst server = createServer();\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n// stderr is safe for logs over stdio (stdout is the protocol channel).\nconsole.error(\"appmockup MCP server running on stdio\");\n"],"mappings":";;;;;;AAAA,SAAS,4BAA4B;AAGrC,IAAM,SAAS,aAAa;AAC5B,IAAM,YAAY,IAAI,qBAAqB;AAC3C,MAAM,OAAO,QAAQ,SAAS;AAE9B,QAAQ,MAAM,uCAAuC;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@appmockup/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for AppMockup — generate App Store screenshot mockups from any AI assistant.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Yakup Bülbül",
|
|
7
|
+
"keywords": ["app-store", "screenshots", "mockup", "mcp", "model-context-protocol"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/yakupbulbul/AppMockup.git",
|
|
11
|
+
"directory": "packages/mcp"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/yakupbulbul/AppMockup#readme",
|
|
14
|
+
"bugs": "https://github.com/yakupbulbul/AppMockup/issues",
|
|
15
|
+
"publishConfig": { "access": "public" },
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/server.js",
|
|
18
|
+
"module": "./dist/server.js",
|
|
19
|
+
"types": "./dist/server.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/server.d.ts",
|
|
23
|
+
"import": "./dist/server.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"appmockup-mcp": "./dist/stdio.js",
|
|
28
|
+
"appmockup-mcp-http": "./dist/http.js"
|
|
29
|
+
},
|
|
30
|
+
"files": ["dist"],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@appmockup/core": "workspace:*",
|
|
38
|
+
"@appmockup/node-render": "workspace:*",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
40
|
+
"zod": "^3.23.8",
|
|
41
|
+
"zod-to-json-schema": "^3.24.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.10.2",
|
|
45
|
+
"tsup": "^8.3.5",
|
|
46
|
+
"typescript": "^5.7.2"
|
|
47
|
+
}
|
|
48
|
+
}
|