@sketchxflow/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 +62 -0
- package/dist/client.js +58 -0
- package/dist/index.js +223 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KriyaX Labs
|
|
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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @sketchxflow/mcp
|
|
2
|
+
|
|
3
|
+
Design websites and mobile apps with [SketchxFlow](https://sketchxflow.com) from
|
|
4
|
+
Claude, Cursor, or any MCP client — by prompt, or by planning the structure first.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
1. **Get an API key:** sign in at [sketchxflow.com](https://sketchxflow.com) →
|
|
9
|
+
**Account → API keys** → *Generate key*. Copy it (shown once).
|
|
10
|
+
|
|
11
|
+
2. **Add the server to your MCP client config:**
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"sketchxflow": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "@sketchxflow/mcp"],
|
|
19
|
+
"env": { "SKETCHXFLOW_API_KEY": "sk_live_…" }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **Claude Desktop:** `claude_desktop_config.json`
|
|
26
|
+
- **Claude Code:** `claude mcp add sketchxflow -e SKETCHXFLOW_API_KEY=sk_live_… -- npx -y @sketchxflow/mcp`
|
|
27
|
+
- **Cursor:** `.cursor/mcp.json`
|
|
28
|
+
|
|
29
|
+
3. Restart the client. Ask it to *"design a landing page for a coffee subscription startup"* and watch it build.
|
|
30
|
+
|
|
31
|
+
## Tools
|
|
32
|
+
|
|
33
|
+
| Tool | What it does |
|
|
34
|
+
|------|--------------|
|
|
35
|
+
| `sketchxflow_design` | One-shot: prompt → full multi-page design → preview URL. |
|
|
36
|
+
| `sketchxflow_plan` | Create a site **plan** (pages + purpose) to review before building. |
|
|
37
|
+
| `sketchxflow_plan_refine` | Adjust a plan ("add a pricing page", "merge About and Team"). |
|
|
38
|
+
| `sketchxflow_generate_from_plan` | Build the approved plan into a design. |
|
|
39
|
+
| `sketchxflow_get_project` | Status + preview URL + page list for a project. |
|
|
40
|
+
| `sketchxflow_list_projects` | List your projects. |
|
|
41
|
+
|
|
42
|
+
Designs are created in **your** SketchxFlow account — they appear in your
|
|
43
|
+
dashboard, charge your own coin balance, and you can edit, export, or publish
|
|
44
|
+
them from the web app. Generation takes ~1–9 minutes; the design/build tools
|
|
45
|
+
stream progress and return when ready.
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Env var | Required | Default | Notes |
|
|
50
|
+
|---------|----------|---------|-------|
|
|
51
|
+
| `SKETCHXFLOW_API_KEY` | yes | — | Your `sk_live_…` key. |
|
|
52
|
+
| `SKETCHXFLOW_API_URL` | no | `https://sketchxflow.com` | Override the API base (e.g. self-host / staging). |
|
|
53
|
+
|
|
54
|
+
## Develop
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install
|
|
58
|
+
npm run build # tsc → dist/
|
|
59
|
+
node dist/index.js # runs over stdio (expects an MCP client on stdin/stdout)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
MIT licensed.
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin REST client for the SketchxFlow MCP API (/api/mcp/*).
|
|
3
|
+
*
|
|
4
|
+
* Auth: a per-user API key (`sk_live_…`) from the SketchxFlow dashboard
|
|
5
|
+
* (Account → API keys), passed as a Bearer token. Configure via env:
|
|
6
|
+
* SKETCHXFLOW_API_KEY (required)
|
|
7
|
+
* SKETCHXFLOW_API_URL (optional, default https://sketchxflow.com)
|
|
8
|
+
*/
|
|
9
|
+
const BASE = (process.env.SKETCHXFLOW_API_URL || "https://sketchxflow.com").replace(/\/+$/, "");
|
|
10
|
+
const KEY = process.env.SKETCHXFLOW_API_KEY || "";
|
|
11
|
+
export function hasKey() {
|
|
12
|
+
return Boolean(KEY);
|
|
13
|
+
}
|
|
14
|
+
async function req(path, method, body) {
|
|
15
|
+
let res;
|
|
16
|
+
try {
|
|
17
|
+
res = await fetch(`${BASE}${path}`, {
|
|
18
|
+
method,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
Authorization: `Bearer ${KEY}`,
|
|
22
|
+
},
|
|
23
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
throw new Error(`Couldn't reach SketchxFlow (${BASE}): ${e.message}`);
|
|
28
|
+
}
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
let data = {};
|
|
31
|
+
try {
|
|
32
|
+
data = text ? JSON.parse(text) : {};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
data = { detail: text };
|
|
36
|
+
}
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const detail = data?.detail;
|
|
39
|
+
const msg = typeof detail === "string" ? detail : detail ? JSON.stringify(detail) : `HTTP ${res.status}`;
|
|
40
|
+
if (res.status === 401) {
|
|
41
|
+
throw new Error(`${msg}. Check SKETCHXFLOW_API_KEY (generate one at sketchxflow.com → Account → API keys).`);
|
|
42
|
+
}
|
|
43
|
+
if (res.status === 402) {
|
|
44
|
+
throw new Error(`${msg}. You're out of coins — top up at sketchxflow.com → Account → Coins.`);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(msg);
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
export const sxf = {
|
|
51
|
+
design: (b) => req("/api/mcp/design", "POST", b),
|
|
52
|
+
plan: (b) => req("/api/mcp/plan", "POST", b),
|
|
53
|
+
planRefine: (id, instruction) => req(`/api/mcp/plan/${encodeURIComponent(id)}/refine`, "POST", { instruction }),
|
|
54
|
+
planGenerate: (id) => req(`/api/mcp/plan/${encodeURIComponent(id)}/generate`, "POST"),
|
|
55
|
+
job: (id) => req(`/api/mcp/jobs/${encodeURIComponent(id)}`, "GET"),
|
|
56
|
+
project: (id) => req(`/api/mcp/projects/${encodeURIComponent(id)}`, "GET"),
|
|
57
|
+
listProjects: () => req("/api/mcp/projects", "GET"),
|
|
58
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SketchxFlow MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Lets an MCP client (Claude Desktop/Code, Cursor, …) design websites and apps
|
|
6
|
+
* via SketchxFlow — direct prompt→design AND a plan→refine→build flow. Generation
|
|
7
|
+
* runs as a background job on the server; this process polls it to completion and
|
|
8
|
+
* emits MCP progress notifications, so one tool call returns the finished preview
|
|
9
|
+
* URL.
|
|
10
|
+
*
|
|
11
|
+
* Auth: per-user API key in SKETCHXFLOW_API_KEY (Account → API keys).
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { sxf, hasKey } from "./client.js";
|
|
17
|
+
// IMPORTANT: stdout is the JSON-RPC channel — only ever log to stderr.
|
|
18
|
+
const log = (...a) => console.error("[sketchxflow-mcp]", ...a);
|
|
19
|
+
const text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
20
|
+
const errorText = (s) => ({ content: [{ type: "text", text: s }], isError: true });
|
|
21
|
+
/** A progress emitter bound to the current request's progressToken (if any). */
|
|
22
|
+
function progressSender(extra) {
|
|
23
|
+
const token = extra?._meta?.progressToken;
|
|
24
|
+
return async (progress, total, message) => {
|
|
25
|
+
if (token === undefined || typeof extra?.sendNotification !== "function")
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
await extra.sendNotification({
|
|
29
|
+
method: "notifications/progress",
|
|
30
|
+
params: { progressToken: token, progress, total: total || undefined, message },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* progress is best-effort */
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const POLL_MS = 4000;
|
|
39
|
+
const MAX_WAIT_MS = 15 * 60 * 1000;
|
|
40
|
+
async function pollJob(jobId, onProgress, signal) {
|
|
41
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
42
|
+
let lastMsg = "";
|
|
43
|
+
while (Date.now() < deadline) {
|
|
44
|
+
if (signal?.aborted)
|
|
45
|
+
throw new Error("Cancelled.");
|
|
46
|
+
const job = await sxf.job(jobId);
|
|
47
|
+
const p = job.progress;
|
|
48
|
+
if (p?.message && p.message !== lastMsg) {
|
|
49
|
+
lastMsg = p.message;
|
|
50
|
+
await onProgress(p.done || 0, p.total || 0, p.message);
|
|
51
|
+
}
|
|
52
|
+
if (job.status === "completed")
|
|
53
|
+
return job;
|
|
54
|
+
if (job.status === "failed")
|
|
55
|
+
throw new Error(job.error || "Generation failed.");
|
|
56
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
57
|
+
}
|
|
58
|
+
throw new Error("Timed out after 15 minutes waiting for the design.");
|
|
59
|
+
}
|
|
60
|
+
function formatResult(result, projectId, previewUrl) {
|
|
61
|
+
const pid = result?.project_id || projectId;
|
|
62
|
+
const url = result?.preview_url || previewUrl;
|
|
63
|
+
const pages = (result?.pages || []).map((p) => p.name || p.slug);
|
|
64
|
+
return [
|
|
65
|
+
`✅ Design ready${result?.title ? `: ${result.title}` : ""}.`,
|
|
66
|
+
`Preview: ${url}`,
|
|
67
|
+
pages.length ? `Pages (${pages.length}): ${pages.join(", ")}` : "",
|
|
68
|
+
`Project ID: ${pid} — open it in the SketchxFlow dashboard to edit, export, or publish.`,
|
|
69
|
+
].filter(Boolean).join("\n");
|
|
70
|
+
}
|
|
71
|
+
function formatPlan(projectId, plan) {
|
|
72
|
+
const brand = plan?.brand_name || plan?.brand?.name || "";
|
|
73
|
+
const pages = plan?.pages || [];
|
|
74
|
+
const lines = pages.map((p, i) => ` ${i + 1}. ${p.name || p.slug}${p.purpose ? ` — ${p.purpose}` : ""}`);
|
|
75
|
+
return [
|
|
76
|
+
`📋 Plan created${brand ? ` for ${brand}` : ""}.`,
|
|
77
|
+
`Project ID: ${projectId}`,
|
|
78
|
+
`Pages (${pages.length}):`,
|
|
79
|
+
...lines,
|
|
80
|
+
"",
|
|
81
|
+
`Next: refine with sketchxflow_plan_refine (project_id "${projectId}"), or build it now with sketchxflow_generate_from_plan (project_id "${projectId}").`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
const server = new McpServer({ name: "sketchxflow", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
85
|
+
// ── Direct design ────────────────────────────────────────────────────────────
|
|
86
|
+
server.registerTool("sketchxflow_design", {
|
|
87
|
+
title: "Design a website or app",
|
|
88
|
+
description: "Generate a complete multi-page website or mobile-app design from a single prompt and return a live preview URL. Use this for a direct, one-shot design. If the user wants to review and shape the page structure FIRST, use sketchxflow_plan instead. Takes 1–9 minutes; this call streams progress and returns when the design is ready.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
prompt: z.string().describe("The design brief — what to build, for whom, and the desired feel."),
|
|
91
|
+
platform: z.enum(["web", "mobile"]).optional().describe("Target surface. Default: web."),
|
|
92
|
+
ai_images: z.boolean().optional().describe("Generate AI images (default true). false keeps placeholders and saves coins."),
|
|
93
|
+
enable_3d: z.boolean().optional().describe("Opt into a real interactive 3D hero (web only). Default false."),
|
|
94
|
+
inspiration_url: z.string().optional().describe("A website URL to take visual inspiration from."),
|
|
95
|
+
},
|
|
96
|
+
}, async (args, extra) => {
|
|
97
|
+
if (!hasKey())
|
|
98
|
+
return errorText("SKETCHXFLOW_API_KEY is not set. Generate a key at sketchxflow.com → Account → API keys and add it to your MCP config.");
|
|
99
|
+
try {
|
|
100
|
+
const onProgress = progressSender(extra);
|
|
101
|
+
await onProgress(0, 0, "Starting design…");
|
|
102
|
+
const started = await sxf.design({
|
|
103
|
+
prompt: args.prompt,
|
|
104
|
+
platform: args.platform,
|
|
105
|
+
ai_images: args.ai_images,
|
|
106
|
+
enable_3d: args.enable_3d,
|
|
107
|
+
inspiration_url: args.inspiration_url,
|
|
108
|
+
});
|
|
109
|
+
const job = await pollJob(started.job_id, onProgress, extra?.signal);
|
|
110
|
+
return text(formatResult(job.result, started.project_id, started.preview_url));
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return errorText(`Design failed: ${e.message}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// ── Plan → refine → build ────────────────────────────────────────────────────
|
|
117
|
+
server.registerTool("sketchxflow_plan", {
|
|
118
|
+
title: "Plan a site before building",
|
|
119
|
+
description: "Create a structured site PLAN (the pages and what each is for) from a brief, WITHOUT building it yet. Show the returned plan to the user; refine it with sketchxflow_plan_refine; then build it with sketchxflow_generate_from_plan. Use this when the user wants to agree on structure before designing.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
prompt: z.string().describe("The brief to plan from."),
|
|
122
|
+
platform: z.enum(["web", "mobile"]).optional().describe("Target surface. Default: web."),
|
|
123
|
+
},
|
|
124
|
+
}, async (args) => {
|
|
125
|
+
if (!hasKey())
|
|
126
|
+
return errorText("SKETCHXFLOW_API_KEY is not set (Account → API keys).");
|
|
127
|
+
try {
|
|
128
|
+
const r = await sxf.plan({ prompt: args.prompt, platform: args.platform });
|
|
129
|
+
return text(formatPlan(r.project_id, r.plan));
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
return errorText(`Planning failed: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
server.registerTool("sketchxflow_plan_refine", {
|
|
136
|
+
title: "Refine a site plan",
|
|
137
|
+
description: "Apply a change to an existing plan (created by sketchxflow_plan) and return the updated plan. E.g. 'add a pricing page', 'merge About and Team', 'make it 5 pages max'.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
project_id: z.string().describe("The project_id returned by sketchxflow_plan."),
|
|
140
|
+
instruction: z.string().describe("How to change the plan."),
|
|
141
|
+
},
|
|
142
|
+
}, async (args) => {
|
|
143
|
+
if (!hasKey())
|
|
144
|
+
return errorText("SKETCHXFLOW_API_KEY is not set (Account → API keys).");
|
|
145
|
+
try {
|
|
146
|
+
const r = await sxf.planRefine(args.project_id, args.instruction);
|
|
147
|
+
return text(formatPlan(r.project_id, r.plan));
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
return errorText(`Refine failed: ${e.message}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
server.registerTool("sketchxflow_generate_from_plan", {
|
|
154
|
+
title: "Build the site from its plan",
|
|
155
|
+
description: "Build the full design from a plan the user has approved (created via sketchxflow_plan / refined via sketchxflow_plan_refine). Returns a live preview URL. Takes 1–9 minutes; streams progress.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
project_id: z.string().describe("The project_id of the planned project."),
|
|
158
|
+
},
|
|
159
|
+
}, async (args, extra) => {
|
|
160
|
+
if (!hasKey())
|
|
161
|
+
return errorText("SKETCHXFLOW_API_KEY is not set (Account → API keys).");
|
|
162
|
+
try {
|
|
163
|
+
const onProgress = progressSender(extra);
|
|
164
|
+
await onProgress(0, 0, "Starting build…");
|
|
165
|
+
const started = await sxf.planGenerate(args.project_id);
|
|
166
|
+
const job = await pollJob(started.job_id, onProgress, extra?.signal);
|
|
167
|
+
return text(formatResult(job.result, started.project_id, started.preview_url));
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return errorText(`Build failed: ${e.message}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// ── Read ─────────────────────────────────────────────────────────────────────
|
|
174
|
+
server.registerTool("sketchxflow_get_project", {
|
|
175
|
+
title: "Get a project's status",
|
|
176
|
+
description: "Return a project's status, preview URL, and page list by project_id.",
|
|
177
|
+
inputSchema: { project_id: z.string().describe("The project_id.") },
|
|
178
|
+
}, async (args) => {
|
|
179
|
+
if (!hasKey())
|
|
180
|
+
return errorText("SKETCHXFLOW_API_KEY is not set (Account → API keys).");
|
|
181
|
+
try {
|
|
182
|
+
const p = await sxf.project(args.project_id);
|
|
183
|
+
const pages = (p.pages || []).map((x) => x.name || x.slug);
|
|
184
|
+
return text([
|
|
185
|
+
`${p.name || "Project"}${p.title ? ` — ${p.title}` : ""}`,
|
|
186
|
+
`Status: ${p.has_html ? "built" : "not built yet"}`,
|
|
187
|
+
`Preview: ${p.preview_url}`,
|
|
188
|
+
pages.length ? `Pages (${pages.length}): ${pages.join(", ")}` : "",
|
|
189
|
+
].filter(Boolean).join("\n"));
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
return errorText(`Lookup failed: ${e.message}`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
server.registerTool("sketchxflow_list_projects", {
|
|
196
|
+
title: "List your projects",
|
|
197
|
+
description: "List the projects in your SketchxFlow account (most recent first).",
|
|
198
|
+
inputSchema: {},
|
|
199
|
+
}, async () => {
|
|
200
|
+
if (!hasKey())
|
|
201
|
+
return errorText("SKETCHXFLOW_API_KEY is not set (Account → API keys).");
|
|
202
|
+
try {
|
|
203
|
+
const r = await sxf.listProjects();
|
|
204
|
+
const rows = (r.projects || []).slice(0, 50);
|
|
205
|
+
if (!rows.length)
|
|
206
|
+
return text("No projects yet. Use sketchxflow_design or sketchxflow_plan to create one.");
|
|
207
|
+
return text(rows.map((p) => `• ${p.name || p.project_id} — ${p.preview_url}`).join("\n"));
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return errorText(`List failed: ${e.message}`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
async function main() {
|
|
214
|
+
if (!hasKey())
|
|
215
|
+
log("warning: SKETCHXFLOW_API_KEY is not set — tools will return an auth error until it is.");
|
|
216
|
+
const transport = new StdioServerTransport();
|
|
217
|
+
await server.connect(transport);
|
|
218
|
+
log("ready");
|
|
219
|
+
}
|
|
220
|
+
main().catch((e) => {
|
|
221
|
+
log("fatal:", e.message);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sketchxflow/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SketchxFlow MCP server — design websites and apps from Claude, Cursor, and any MCP client.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sketchxflow-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepare": "npm run build",
|
|
19
|
+
"start": "node dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"sketchxflow",
|
|
24
|
+
"ai",
|
|
25
|
+
"design",
|
|
26
|
+
"website-builder",
|
|
27
|
+
"model-context-protocol"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
+
"zod": "^3.25.76"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.0.0",
|
|
36
|
+
"typescript": "^6.0.3"
|
|
37
|
+
}
|
|
38
|
+
}
|